eslint-plugin-markdown-preferences 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -109,13 +109,20 @@ function getThematicBreakMarker(sourceCode, node) {
109
109
  * Get the source location from a range in a node.
110
110
  */
111
111
  function getSourceLocationFromRange(sourceCode, node, range) {
112
- const [nodeStart] = sourceCode.getRange(node);
112
+ const nodeRange = sourceCode.getRange(node);
113
+ const loc = sourceCode.getLoc(node);
114
+ if (nodeRange[1] <= range[0]) return getSourceLocationFromRangeAndSourcePosition(sourceCode, nodeRange[1], loc.end, range);
115
+ return getSourceLocationFromRangeAndSourcePosition(sourceCode, nodeRange[0], loc.start, range);
116
+ }
117
+ /**
118
+ * Get the source location from a range
119
+ */
120
+ function getSourceLocationFromRangeAndSourcePosition(sourceCode, startIndex, startLoc, range) {
113
121
  let startLine, startColumn;
114
- if (nodeStart <= range[0]) {
115
- const loc = sourceCode.getLoc(node);
116
- const beforeLines = sourceCode.text.slice(nodeStart, range[0]).split(/\n/u);
117
- startLine = loc.start.line + beforeLines.length - 1;
118
- startColumn = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
122
+ if (startIndex <= range[0]) {
123
+ const beforeLines = sourceCode.text.slice(startIndex, range[0]).split(/\n/u);
124
+ startLine = startLoc.line + beforeLines.length - 1;
125
+ startColumn = (beforeLines.length === 1 ? startLoc.column : 1) + (beforeLines.at(-1) || "").length;
119
126
  } else {
120
127
  const beforeLines = sourceCode.text.slice(0, range[0]).split(/\n/u);
121
128
  startLine = beforeLines.length;
@@ -364,15 +371,23 @@ function getParsedLines(sourceCode) {
364
371
  }
365
372
 
366
373
  //#endregion
367
- //#region src/utils/get-text-width.ts
374
+ //#region src/utils/text-width.ts
368
375
  let segmenter;
369
376
  /**
370
377
  * Get the width of a text string.
371
378
  */
372
- function getTextWidth(text) {
373
- if (!text.includes(" ")) return stringWidth(text);
379
+ function getTextWidth(text, start = 0, end = text.length) {
380
+ if (!text.includes(" ")) return stringWidth(text.slice(start, end));
374
381
  if (!segmenter) segmenter = new Intl.Segmenter("en");
375
- let width = 0;
382
+ const prefixWidth = getTextWidthBySegment(text.slice(0, start), 0);
383
+ return getTextWidthBySegment(text.slice(start, end), prefixWidth);
384
+ }
385
+ /**
386
+ * Get the width of a text string by segmenter.
387
+ */
388
+ function getTextWidthBySegment(text, startWidth) {
389
+ if (!segmenter) segmenter = new Intl.Segmenter("en");
390
+ let width = startWidth;
376
391
  for (const { segment: c } of segmenter.segment(text)) if (c === " ") width += 4 - width % 4;
377
392
  else width += stringWidth(c);
378
393
  return width;
@@ -386,7 +401,7 @@ var atx_heading_closing_sequence_length_default = createRule("atx-heading-closin
386
401
  docs: {
387
402
  description: "enforce consistent length for the closing sequence (trailing #s) in ATX headings.",
388
403
  categories: ["standard"],
389
- listCategory: "Stylistic"
404
+ listCategory: "Decorative"
390
405
  },
391
406
  fixable: "code",
392
407
  hasSuggestions: false,
@@ -524,7 +539,7 @@ var atx_heading_closing_sequence_default = createRule("atx-heading-closing-seque
524
539
  docs: {
525
540
  description: "enforce consistent use of closing sequence in ATX headings.",
526
541
  categories: ["standard"],
527
- listCategory: "Stylistic"
542
+ listCategory: "Decorative"
528
543
  },
529
544
  fixable: "code",
530
545
  hasSuggestions: false,
@@ -700,7 +715,7 @@ var blockquote_marker_alignment_default = createRule("blockquote-marker-alignmen
700
715
  docs: {
701
716
  description: "enforce consistent alignment of blockquote markers",
702
717
  categories: ["recommended", "standard"],
703
- listCategory: "Stylistic"
718
+ listCategory: "Whitespace"
704
719
  },
705
720
  fixable: "whitespace",
706
721
  hasSuggestions: false,
@@ -783,7 +798,7 @@ function getOtherMarker(unavailableMarker) {
783
798
  /**
784
799
  * Parse rule options.
785
800
  */
786
- function parseOptions$3(options) {
801
+ function parseOptions$4(options) {
787
802
  const primary = options.primary || "-";
788
803
  const secondary = options.secondary || getOtherMarker(primary);
789
804
  if (primary === secondary) throw new Error(`\`primary\` and \`secondary\` cannot be the same (primary: "${primary}", secondary: "${secondary}").`);
@@ -815,7 +830,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
815
830
  docs: {
816
831
  description: "enforce consistent bullet list (unordered list) marker style",
817
832
  categories: ["standard"],
818
- listCategory: "Stylistic"
833
+ listCategory: "Notation"
819
834
  },
820
835
  fixable: "code",
821
836
  hasSuggestions: false,
@@ -851,7 +866,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
851
866
  },
852
867
  create(context) {
853
868
  const sourceCode = context.sourceCode;
854
- const options = parseOptions$3(context.options[0] || {});
869
+ const options = parseOptions$4(context.options[0] || {});
855
870
  let containerStack = {
856
871
  node: sourceCode.ast,
857
872
  level: 1,
@@ -1114,7 +1129,7 @@ var code_fence_length_default = createRule("code-fence-length", {
1114
1129
  docs: {
1115
1130
  description: "enforce consistent code fence length in fenced code blocks.",
1116
1131
  categories: ["standard"],
1117
- listCategory: "Stylistic"
1132
+ listCategory: "Decorative"
1118
1133
  },
1119
1134
  fixable: "code",
1120
1135
  hasSuggestions: false,
@@ -1251,7 +1266,7 @@ var code_fence_style_default = createRule("code-fence-style", {
1251
1266
  docs: {
1252
1267
  description: "enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks.",
1253
1268
  categories: ["standard"],
1254
- listCategory: "Stylistic"
1269
+ listCategory: "Notation"
1255
1270
  },
1256
1271
  fixable: "code",
1257
1272
  hasSuggestions: false,
@@ -1298,7 +1313,7 @@ var definitions_last_default = createRule("definitions-last", {
1298
1313
  docs: {
1299
1314
  description: "require link definitions and footnote definitions to be placed at the end of the document",
1300
1315
  categories: [],
1301
- listCategory: "Stylistic"
1316
+ listCategory: "Notation"
1302
1317
  },
1303
1318
  fixable: "code",
1304
1319
  hasSuggestions: false,
@@ -3422,7 +3437,7 @@ var emphasis_delimiters_style_default = createRule("emphasis-delimiters-style",
3422
3437
  docs: {
3423
3438
  description: "enforce a consistent delimiter style for emphasis and strong emphasis",
3424
3439
  categories: ["standard"],
3425
- listCategory: "Stylistic"
3440
+ listCategory: "Notation"
3426
3441
  },
3427
3442
  fixable: "code",
3428
3443
  hasSuggestions: false,
@@ -3604,7 +3619,7 @@ var hard_linebreak_style_default = createRule("hard-linebreak-style", {
3604
3619
  docs: {
3605
3620
  description: "enforce consistent hard linebreak style.",
3606
3621
  categories: ["recommended", "standard"],
3607
- listCategory: "Stylistic"
3622
+ listCategory: "Notation"
3608
3623
  },
3609
3624
  fixable: "code",
3610
3625
  hasSuggestions: false,
@@ -5037,7 +5052,7 @@ function parseListItem(sourceCode, node) {
5037
5052
  /**
5038
5053
  * Parse options.
5039
5054
  */
5040
- function parseOptions$2(options) {
5055
+ function parseOptions$3(options) {
5041
5056
  const listItems = options?.listItems;
5042
5057
  return { listItems: {
5043
5058
  first: listItems?.first ?? 1,
@@ -5051,7 +5066,7 @@ var indent_default = createRule("indent", {
5051
5066
  docs: {
5052
5067
  description: "enforce consistent indentation in Markdown files",
5053
5068
  categories: ["standard"],
5054
- listCategory: "Stylistic"
5069
+ listCategory: "Whitespace"
5055
5070
  },
5056
5071
  fixable: "whitespace",
5057
5072
  hasSuggestions: false,
@@ -5090,7 +5105,7 @@ var indent_default = createRule("indent", {
5090
5105
  },
5091
5106
  create(context) {
5092
5107
  const sourceCode = context.sourceCode;
5093
- const options = parseOptions$2(context.options[0]);
5108
+ const options = parseOptions$3(context.options[0]);
5094
5109
  class AbsBlockStack {
5095
5110
  violations = [];
5096
5111
  getCurrentBlockquote() {
@@ -6039,7 +6054,7 @@ var level1_heading_style_default = createRule("level1-heading-style", {
6039
6054
  docs: {
6040
6055
  description: "enforce consistent style for level 1 headings",
6041
6056
  categories: ["standard"],
6042
- listCategory: "Stylistic"
6057
+ listCategory: "Notation"
6043
6058
  },
6044
6059
  fixable: "code",
6045
6060
  hasSuggestions: false,
@@ -6116,7 +6131,7 @@ var level2_heading_style_default = createRule("level2-heading-style", {
6116
6131
  docs: {
6117
6132
  description: "enforce consistent style for level 2 headings",
6118
6133
  categories: ["standard"],
6119
- listCategory: "Stylistic"
6134
+ listCategory: "Notation"
6120
6135
  },
6121
6136
  fixable: "code",
6122
6137
  hasSuggestions: false,
@@ -6193,7 +6208,7 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6193
6208
  docs: {
6194
6209
  description: "enforce linebreaks after opening and before closing link brackets",
6195
6210
  categories: ["standard"],
6196
- listCategory: "Stylistic"
6211
+ listCategory: "Whitespace"
6197
6212
  },
6198
6213
  fixable: "whitespace",
6199
6214
  hasSuggestions: false,
@@ -6218,11 +6233,11 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6218
6233
  },
6219
6234
  create(context) {
6220
6235
  const sourceCode = context.sourceCode;
6221
- const optionProvider = parseOptions$4(context.options[0]);
6236
+ const optionProvider = parseOptions$5(context.options[0]);
6222
6237
  /**
6223
6238
  * Parse the options.
6224
6239
  */
6225
- function parseOptions$4(option) {
6240
+ function parseOptions$5(option) {
6226
6241
  const newline = option?.newline ?? "never";
6227
6242
  const multiline = option?.multiline ?? false;
6228
6243
  return (bracketsRange) => {
@@ -6353,7 +6368,7 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6353
6368
  /**
6354
6369
  * The basic option for links and images.
6355
6370
  */
6356
- function parseOptions$1(option) {
6371
+ function parseOptions$2(option) {
6357
6372
  const space = option?.space ?? "never";
6358
6373
  const imagesInLinks = option?.imagesInLinks;
6359
6374
  return {
@@ -6382,7 +6397,7 @@ var link_bracket_spacing_default = createRule("link-bracket-spacing", {
6382
6397
  docs: {
6383
6398
  description: "enforce consistent spacing inside link brackets",
6384
6399
  categories: ["standard"],
6385
- listCategory: "Stylistic"
6400
+ listCategory: "Whitespace"
6386
6401
  },
6387
6402
  fixable: "whitespace",
6388
6403
  hasSuggestions: false,
@@ -6403,7 +6418,7 @@ var link_bracket_spacing_default = createRule("link-bracket-spacing", {
6403
6418
  },
6404
6419
  create(context) {
6405
6420
  const sourceCode = context.sourceCode;
6406
- const options = parseOptions$1(context.options[0]);
6421
+ const options = parseOptions$2(context.options[0]);
6407
6422
  /**
6408
6423
  * Verify the space after the opening bracket and before the closing bracket.
6409
6424
  */
@@ -6546,7 +6561,7 @@ var link_destination_style_default = createRule("link-destination-style", {
6546
6561
  docs: {
6547
6562
  description: "enforce a consistent style for link destinations",
6548
6563
  categories: ["standard"],
6549
- listCategory: "Stylistic"
6564
+ listCategory: "Notation"
6550
6565
  },
6551
6566
  fixable: "code",
6552
6567
  hasSuggestions: false,
@@ -6671,7 +6686,7 @@ var link_paren_newline_default = createRule("link-paren-newline", {
6671
6686
  docs: {
6672
6687
  description: "enforce linebreaks after opening and before closing link parentheses",
6673
6688
  categories: ["standard"],
6674
- listCategory: "Stylistic"
6689
+ listCategory: "Whitespace"
6675
6690
  },
6676
6691
  fixable: "whitespace",
6677
6692
  hasSuggestions: false,
@@ -6696,11 +6711,11 @@ var link_paren_newline_default = createRule("link-paren-newline", {
6696
6711
  },
6697
6712
  create(context) {
6698
6713
  const sourceCode = context.sourceCode;
6699
- const optionProvider = parseOptions$4(context.options[0]);
6714
+ const optionProvider = parseOptions$5(context.options[0]);
6700
6715
  /**
6701
6716
  * Parse the options.
6702
6717
  */
6703
- function parseOptions$4(option) {
6718
+ function parseOptions$5(option) {
6704
6719
  const newline = option?.newline ?? "never";
6705
6720
  const multiline = option?.multiline ?? false;
6706
6721
  return (openingParenIndex, closingParenIndex) => {
@@ -6815,7 +6830,7 @@ var link_paren_spacing_default = createRule("link-paren-spacing", {
6815
6830
  docs: {
6816
6831
  description: "enforce consistent spacing inside link parentheses",
6817
6832
  categories: ["standard"],
6818
- listCategory: "Stylistic"
6833
+ listCategory: "Whitespace"
6819
6834
  },
6820
6835
  fixable: "whitespace",
6821
6836
  hasSuggestions: false,
@@ -6950,7 +6965,7 @@ var link_title_style_default = createRule("link-title-style", {
6950
6965
  docs: {
6951
6966
  description: "enforce a consistent style for link titles",
6952
6967
  categories: ["standard"],
6953
- listCategory: "Stylistic"
6968
+ listCategory: "Notation"
6954
6969
  },
6955
6970
  fixable: "code",
6956
6971
  hasSuggestions: false,
@@ -7032,7 +7047,7 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
7032
7047
  docs: {
7033
7048
  description: "enforce consistent alignment of list markers",
7034
7049
  categories: ["recommended", "standard"],
7035
- listCategory: "Stylistic"
7050
+ listCategory: "Whitespace"
7036
7051
  },
7037
7052
  fixable: "whitespace",
7038
7053
  hasSuggestions: false,
@@ -7131,7 +7146,7 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
7131
7146
  docs: {
7132
7147
  description: "disallow laziness in blockquotes",
7133
7148
  categories: ["recommended", "standard"],
7134
- listCategory: "Stylistic"
7149
+ listCategory: "Decorative"
7135
7150
  },
7136
7151
  fixable: void 0,
7137
7152
  hasSuggestions: true,
@@ -7230,6 +7245,193 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
7230
7245
  }
7231
7246
  });
7232
7247
 
7248
+ //#endregion
7249
+ //#region src/utils/table.ts
7250
+ /**
7251
+ * Parse the table.
7252
+ */
7253
+ function parseTable(sourceCode, node) {
7254
+ const headerRow = parseTableRow(sourceCode, node.children[0]);
7255
+ if (!headerRow) return null;
7256
+ const delimiterRow = parseTableDelimiterRow(sourceCode, node);
7257
+ if (!delimiterRow) return null;
7258
+ const bodyRows = [];
7259
+ for (const child of node.children.slice(1)) {
7260
+ const bodyRow = parseTableRow(sourceCode, child);
7261
+ if (!bodyRow) return null;
7262
+ bodyRows.push(bodyRow);
7263
+ }
7264
+ return {
7265
+ headerRow,
7266
+ delimiterRow,
7267
+ bodyRows
7268
+ };
7269
+ }
7270
+ /**
7271
+ * Parse the table delimiter row.
7272
+ */
7273
+ function parseTableDelimiterRow(sourceCode, node) {
7274
+ const headerRow = node.children[0];
7275
+ const headerRange = sourceCode.getRange(headerRow);
7276
+ const delimiterEndIndex = node.children.length > 1 ? sourceCode.getRange(node.children[1])[0] : sourceCode.getRange(node)[1];
7277
+ const delimiterText = sourceCode.text.slice(headerRange[1], delimiterEndIndex);
7278
+ const parsed = parseTableDelimiterRowFromText(delimiterText);
7279
+ if (!parsed) return null;
7280
+ const delimiters = parsed.delimiters.map((d) => {
7281
+ let leadingPipe = null;
7282
+ if (d.leadingPipe) {
7283
+ const leadingPipeRange = [headerRange[1] + d.leadingPipe.range[0], headerRange[1] + d.leadingPipe.range[1]];
7284
+ leadingPipe = {
7285
+ text: d.leadingPipe.text,
7286
+ range: leadingPipeRange,
7287
+ loc: getSourceLocationFromRange(sourceCode, headerRow, leadingPipeRange)
7288
+ };
7289
+ }
7290
+ const delimiterRange = [headerRange[1] + d.delimiter.range[0], headerRange[1] + d.delimiter.range[1]];
7291
+ return {
7292
+ leadingPipe,
7293
+ delimiter: {
7294
+ text: d.delimiter.text,
7295
+ align: d.delimiter.text.startsWith(":") ? d.delimiter.text.endsWith(":") ? "center" : "left" : d.delimiter.text.endsWith(":") ? "right" : "none",
7296
+ range: delimiterRange,
7297
+ loc: getSourceLocationFromRange(sourceCode, headerRow, delimiterRange)
7298
+ }
7299
+ };
7300
+ });
7301
+ let trailingPipe = null;
7302
+ if (parsed.trailingPipe) {
7303
+ const trailingPipeRange = [headerRange[1] + parsed.trailingPipe.range[0], headerRange[1] + parsed.trailingPipe.range[1]];
7304
+ trailingPipe = {
7305
+ text: parsed.trailingPipe.text,
7306
+ range: trailingPipeRange,
7307
+ loc: getSourceLocationFromRange(sourceCode, headerRow, trailingPipeRange)
7308
+ };
7309
+ }
7310
+ const firstToken = delimiters[0].leadingPipe ?? delimiters[0].delimiter;
7311
+ const lastToken = trailingPipe ?? delimiters[delimiters.length - 1].delimiter;
7312
+ return {
7313
+ delimiters,
7314
+ trailingPipe,
7315
+ range: [firstToken.range[0], lastToken.range[1]],
7316
+ loc: {
7317
+ start: firstToken.loc.start,
7318
+ end: lastToken.loc.end
7319
+ }
7320
+ };
7321
+ }
7322
+ /**
7323
+ * Parse the table row.
7324
+ */
7325
+ function parseTableRow(sourceCode, node) {
7326
+ const cells = [];
7327
+ let trailingPipe = null;
7328
+ for (const cell of node.children) {
7329
+ const cellRange = sourceCode.getRange(cell);
7330
+ const cellLoc = sourceCode.getLoc(cell);
7331
+ const leadingPipe = sourceCode.text[cellRange[0]] === "|" ? {
7332
+ text: "|",
7333
+ range: [cellRange[0], cellRange[0] + 1],
7334
+ loc: {
7335
+ start: cellLoc.start,
7336
+ end: {
7337
+ line: cellLoc.start.line,
7338
+ column: cellLoc.start.column + 1
7339
+ }
7340
+ }
7341
+ } : null;
7342
+ if (trailingPipe && leadingPipe) return null;
7343
+ let parsedCell = null;
7344
+ if (cell.children.length > 0) {
7345
+ const firstChild = cell.children[0];
7346
+ const lastChild = cell.children[cell.children.length - 1];
7347
+ parsedCell = {
7348
+ range: [sourceCode.getRange(firstChild)[0], sourceCode.getRange(lastChild)[1]],
7349
+ loc: {
7350
+ start: sourceCode.getLoc(firstChild).start,
7351
+ end: sourceCode.getLoc(lastChild).end
7352
+ }
7353
+ };
7354
+ }
7355
+ cells.push({
7356
+ leadingPipe,
7357
+ cell: parsedCell
7358
+ });
7359
+ trailingPipe = sourceCode.text[cellRange[1] - 1] === "|" ? {
7360
+ text: "|",
7361
+ range: [cellRange[1] - 1, cellRange[1]],
7362
+ loc: {
7363
+ start: {
7364
+ line: cellLoc.end.line,
7365
+ column: cellLoc.end.column - 1
7366
+ },
7367
+ end: cellLoc.end
7368
+ }
7369
+ } : null;
7370
+ }
7371
+ const firstToken = cells[0].leadingPipe ?? cells[0].cell;
7372
+ const lastToken = trailingPipe ?? cells[cells.length - 1].cell ?? cells[cells.length - 1].leadingPipe;
7373
+ return {
7374
+ cells,
7375
+ trailingPipe,
7376
+ range: [firstToken.range[0], lastToken.range[1]],
7377
+ loc: {
7378
+ start: firstToken.loc.start,
7379
+ end: lastToken.loc.end
7380
+ }
7381
+ };
7382
+ }
7383
+ /**
7384
+ * Parse the table delimiter row from the text.
7385
+ */
7386
+ function parseTableDelimiterRowFromText(text) {
7387
+ const cursor = new ForwardCharacterCursor(text);
7388
+ cursor.skipSpaces();
7389
+ while (cursor.curr() === ">") {
7390
+ cursor.next();
7391
+ cursor.skipSpaces();
7392
+ }
7393
+ const delimiters = [];
7394
+ let pipe = consumePipe();
7395
+ while (!cursor.finished()) {
7396
+ const delimiterStart = cursor.currIndex();
7397
+ cursor.skipUntilEnd((c) => c === "|" || isSpaceOrTab(c) || c === "\n" || c === "\r");
7398
+ const delimiterRange = [delimiterStart, cursor.currIndex()];
7399
+ const delimiterText = text.slice(...delimiterRange);
7400
+ if (!/^:?-+:?$/u.test(delimiterText)) return null;
7401
+ if (delimiters.length > 0 && pipe == null) return null;
7402
+ delimiters.push({
7403
+ leadingPipe: pipe,
7404
+ delimiter: {
7405
+ text: delimiterText,
7406
+ range: delimiterRange
7407
+ }
7408
+ });
7409
+ pipe = consumePipe();
7410
+ }
7411
+ return {
7412
+ delimiters,
7413
+ trailingPipe: pipe
7414
+ };
7415
+ /**
7416
+ * Consume a pipe if exists.
7417
+ */
7418
+ function consumePipe() {
7419
+ cursor.skipSpaces();
7420
+ if (cursor.curr() === "|") {
7421
+ const pipeStart = cursor.currIndex();
7422
+ cursor.next();
7423
+ const pipeRange = [pipeStart, cursor.currIndex()];
7424
+ const result = {
7425
+ text: text.slice(...pipeRange),
7426
+ range: pipeRange
7427
+ };
7428
+ cursor.skipSpaces();
7429
+ return result;
7430
+ }
7431
+ return null;
7432
+ }
7433
+ }
7434
+
7233
7435
  //#endregion
7234
7436
  //#region src/rules/no-multi-spaces.ts
7235
7437
  var no_multi_spaces_default = createRule("no-multi-spaces", {
@@ -7238,7 +7440,7 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7238
7440
  docs: {
7239
7441
  description: "disallow multiple spaces",
7240
7442
  categories: ["standard"],
7241
- listCategory: "Stylistic"
7443
+ listCategory: "Whitespace"
7242
7444
  },
7243
7445
  fixable: "whitespace",
7244
7446
  hasSuggestions: false,
@@ -7258,7 +7460,8 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7258
7460
  linkReference: verifyLinkReference,
7259
7461
  listItem: verifyListItem,
7260
7462
  blockquote: processBlockquote,
7261
- text: verifyText
7463
+ text: verifyText,
7464
+ table: verifyTable
7262
7465
  };
7263
7466
  /**
7264
7467
  * Verify a text node.
@@ -7267,6 +7470,14 @@ var no_multi_spaces_default = createRule("no-multi-spaces", {
7267
7470
  verifyTextInNode(node);
7268
7471
  }
7269
7472
  /**
7473
+ * Verify a table node.
7474
+ */
7475
+ function verifyTable(node) {
7476
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
7477
+ if (!parsedDelimiterRow) return;
7478
+ verifyTextInRange(node, parsedDelimiterRow.range);
7479
+ }
7480
+ /**
7270
7481
  * Verify a definition node.
7271
7482
  */
7272
7483
  function verifyLinkDefinition(node) {
@@ -7442,7 +7653,7 @@ var no_multiple_empty_lines_default = createRule("no-multiple-empty-lines", {
7442
7653
  docs: {
7443
7654
  description: "disallow multiple empty lines in Markdown files.",
7444
7655
  categories: ["standard"],
7445
- listCategory: "Stylistic"
7656
+ listCategory: "Whitespace"
7446
7657
  },
7447
7658
  fixable: "whitespace",
7448
7659
  hasSuggestions: false,
@@ -7601,7 +7812,7 @@ var no_text_backslash_linebreak_default = createRule("no-text-backslash-linebrea
7601
7812
  docs: {
7602
7813
  description: "disallow text backslash at the end of a line.",
7603
7814
  categories: ["recommended", "standard"],
7604
- listCategory: "Stylistic"
7815
+ listCategory: "Notation"
7605
7816
  },
7606
7817
  fixable: void 0,
7607
7818
  hasSuggestions: true,
@@ -7646,7 +7857,7 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
7646
7857
  docs: {
7647
7858
  description: "disallow trailing whitespace at the end of lines in Markdown files.",
7648
7859
  categories: ["standard"],
7649
- listCategory: "Stylistic"
7860
+ listCategory: "Whitespace"
7650
7861
  },
7651
7862
  fixable: "whitespace",
7652
7863
  hasSuggestions: false,
@@ -7772,7 +7983,7 @@ var ordered_list_marker_sequence_default = createRule("ordered-list-marker-seque
7772
7983
  docs: {
7773
7984
  description: "enforce that ordered list markers use sequential numbers",
7774
7985
  categories: ["standard"],
7775
- listCategory: "Stylistic"
7986
+ listCategory: "Decorative"
7776
7987
  },
7777
7988
  fixable: "code",
7778
7989
  hasSuggestions: true,
@@ -7986,7 +8197,7 @@ function markerToKind(marker) {
7986
8197
  /**
7987
8198
  * Parse rule options.
7988
8199
  */
7989
- function parseOptions(options) {
8200
+ function parseOptions$1(options) {
7990
8201
  const prefer = markerToKind(options.prefer) || ".";
7991
8202
  const overrides = (options.overrides ?? []).map((override) => {
7992
8203
  const preferForOverride = markerToKind(override.prefer) || ".";
@@ -8013,7 +8224,7 @@ var ordered_list_marker_style_default = createRule("ordered-list-marker-style",
8013
8224
  docs: {
8014
8225
  description: "enforce consistent ordered list marker style",
8015
8226
  categories: ["standard"],
8016
- listCategory: "Stylistic"
8227
+ listCategory: "Notation"
8017
8228
  },
8018
8229
  fixable: "code",
8019
8230
  hasSuggestions: false,
@@ -8047,7 +8258,7 @@ var ordered_list_marker_style_default = createRule("ordered-list-marker-style",
8047
8258
  },
8048
8259
  create(context) {
8049
8260
  const sourceCode = context.sourceCode;
8050
- const options = parseOptions(context.options[0] || {});
8261
+ const options = parseOptions$1(context.options[0] || {});
8051
8262
  let containerStack = {
8052
8263
  node: sourceCode.ast,
8053
8264
  level: 1,
@@ -8232,7 +8443,7 @@ var padding_line_between_blocks_default = createRule("padding-line-between-block
8232
8443
  docs: {
8233
8444
  description: "require or disallow padding lines between blocks",
8234
8445
  categories: ["standard"],
8235
- listCategory: "Stylistic"
8446
+ listCategory: "Whitespace"
8236
8447
  },
8237
8448
  fixable: "whitespace",
8238
8449
  hasSuggestions: false,
@@ -8430,7 +8641,7 @@ var prefer_autolinks_default = createRule("prefer-autolinks", {
8430
8641
  docs: {
8431
8642
  description: "enforce the use of autolinks for URLs",
8432
8643
  categories: ["recommended", "standard"],
8433
- listCategory: "Stylistic"
8644
+ listCategory: "Notation"
8434
8645
  },
8435
8646
  fixable: "code",
8436
8647
  hasSuggestions: false,
@@ -8468,7 +8679,7 @@ var prefer_fenced_code_blocks_default = createRule("prefer-fenced-code-blocks",
8468
8679
  docs: {
8469
8680
  description: "enforce the use of fenced code blocks over indented code blocks",
8470
8681
  categories: ["recommended", "standard"],
8471
- listCategory: "Stylistic"
8682
+ listCategory: "Notation"
8472
8683
  },
8473
8684
  fixable: "code",
8474
8685
  hasSuggestions: false,
@@ -8698,7 +8909,7 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
8698
8909
  docs: {
8699
8910
  description: "enforce using link reference definitions instead of inline links",
8700
8911
  categories: [],
8701
- listCategory: "Stylistic"
8912
+ listCategory: "Notation"
8702
8913
  },
8703
8914
  fixable: "code",
8704
8915
  hasSuggestions: false,
@@ -8977,9 +9188,9 @@ var setext_heading_underline_length_default = createRule("setext-heading-underli
8977
9188
  docs: {
8978
9189
  description: "enforce setext heading underline length",
8979
9190
  categories: ["standard"],
8980
- listCategory: "Stylistic"
9191
+ listCategory: "Decorative"
8981
9192
  },
8982
- fixable: "whitespace",
9193
+ fixable: "code",
8983
9194
  schema: [{
8984
9195
  type: "object",
8985
9196
  properties: {
@@ -9215,7 +9426,7 @@ var sort_definitions_default = createRule("sort-definitions", {
9215
9426
  docs: {
9216
9427
  description: "enforce a specific order for link definitions and footnote definitions",
9217
9428
  categories: ["standard"],
9218
- listCategory: "Stylistic"
9429
+ listCategory: "Decorative"
9219
9430
  },
9220
9431
  fixable: "code",
9221
9432
  hasSuggestions: false,
@@ -9488,7 +9699,7 @@ var strikethrough_delimiters_style_default = createRule("strikethrough-delimiter
9488
9699
  docs: {
9489
9700
  description: "enforce a consistent delimiter style for strikethrough",
9490
9701
  categories: ["standard"],
9491
- listCategory: "Stylistic"
9702
+ listCategory: "Notation"
9492
9703
  },
9493
9704
  fixable: "code",
9494
9705
  hasSuggestions: false,
@@ -9655,6 +9866,724 @@ var table_header_casing_default = createRule("table-header-casing", {
9655
9866
  }
9656
9867
  });
9657
9868
 
9869
+ //#endregion
9870
+ //#region src/rules/table-leading-trailing-pipes.ts
9871
+ var table_leading_trailing_pipes_default = createRule("table-leading-trailing-pipes", {
9872
+ meta: {
9873
+ type: "layout",
9874
+ docs: {
9875
+ description: "enforce consistent use of leading and trailing pipes in tables.",
9876
+ categories: ["standard"],
9877
+ listCategory: "Decorative"
9878
+ },
9879
+ fixable: "code",
9880
+ hasSuggestions: false,
9881
+ schema: [{ anyOf: [{ enum: ["always", "never"] }, {
9882
+ type: "object",
9883
+ properties: {
9884
+ leading: { enum: ["always", "never"] },
9885
+ trailing: { enum: ["always", "never"] }
9886
+ },
9887
+ additionalProperties: false
9888
+ }] }],
9889
+ messages: {
9890
+ missingLeadingPipe: "Table line should start with a leading pipe.",
9891
+ unexpectedLeadingPipe: "Table line should not start with a leading pipe.",
9892
+ missingTrailingPipe: "Table line should end with a trailing pipe.",
9893
+ unexpectedTrailingPipe: "Table line should not end with a trailing pipe."
9894
+ }
9895
+ },
9896
+ create(context) {
9897
+ const sourceCode = context.sourceCode;
9898
+ const preferOption = context.options[0] ?? "always";
9899
+ const leadingOption = typeof preferOption === "string" ? preferOption : preferOption.leading ?? "always";
9900
+ const trailingOption = typeof preferOption === "string" ? preferOption : preferOption.trailing ?? "always";
9901
+ /**
9902
+ * Verify the table pipes
9903
+ */
9904
+ function verifyTablePipes(node) {
9905
+ for (const row of node.children) verifyTableRowPipes(row);
9906
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
9907
+ if (parsedDelimiterRow) verifyTableLinePipes(parsedDelimiterRow.range, parsedDelimiterRow.loc, parsedDelimiterRow.delimiters.length);
9908
+ }
9909
+ /**
9910
+ * Verify the table row pipes
9911
+ */
9912
+ function verifyTableRowPipes(node) {
9913
+ const loc = sourceCode.getLoc(node);
9914
+ const range = sourceCode.getRange(node);
9915
+ verifyTableLinePipes(range, loc, node.children.length);
9916
+ }
9917
+ /**
9918
+ * Verify the table line pipes
9919
+ */
9920
+ function verifyTableLinePipes(lineContentRange, lineLocation, columnCount) {
9921
+ verifyTableLeadingPipe(lineContentRange, lineLocation, columnCount);
9922
+ verifyTableTrailingPipe(lineContentRange, lineLocation, columnCount);
9923
+ }
9924
+ /**
9925
+ * Verify the table leading pipe
9926
+ */
9927
+ function verifyTableLeadingPipe(lineContentRange, lineLocation, columnCount) {
9928
+ if (leadingOption === "always") {
9929
+ if (sourceCode.text.startsWith("|", lineContentRange[0])) return;
9930
+ context.report({
9931
+ messageId: "missingLeadingPipe",
9932
+ loc: lineLocation.start,
9933
+ fix(fixer) {
9934
+ return fixer.insertTextBeforeRange(lineContentRange, "| ");
9935
+ }
9936
+ });
9937
+ } else if (leadingOption === "never") {
9938
+ if (columnCount < 2) {
9939
+ if (!(trailingOption === "always" && sourceCode.text.endsWith("|", lineContentRange[1]))) return;
9940
+ }
9941
+ if (!sourceCode.text.startsWith("|", lineContentRange[0])) return;
9942
+ let endIndex = lineContentRange[0] + 1;
9943
+ while (endIndex < lineContentRange[1] && isSpaceOrTab(sourceCode.text[endIndex])) endIndex++;
9944
+ context.report({
9945
+ messageId: "unexpectedLeadingPipe",
9946
+ loc: {
9947
+ start: lineLocation.start,
9948
+ end: {
9949
+ line: lineLocation.start.line,
9950
+ column: lineLocation.start.column + (endIndex - lineContentRange[0])
9951
+ }
9952
+ },
9953
+ fix(fixer) {
9954
+ return fixer.removeRange([lineContentRange[0], endIndex]);
9955
+ }
9956
+ });
9957
+ }
9958
+ }
9959
+ /**
9960
+ * Verify the table trailing pipe
9961
+ */
9962
+ function verifyTableTrailingPipe(lineContentRange, lineLocation, columnCount) {
9963
+ if (trailingOption === "always") {
9964
+ if (sourceCode.text.endsWith("|", lineContentRange[1])) return;
9965
+ context.report({
9966
+ messageId: "missingTrailingPipe",
9967
+ loc: lineLocation.end,
9968
+ fix(fixer) {
9969
+ return fixer.insertTextAfterRange(lineContentRange, " |");
9970
+ }
9971
+ });
9972
+ } else if (trailingOption === "never") {
9973
+ if (columnCount < 2) {
9974
+ if (!(leadingOption === "always" && sourceCode.text.startsWith("|", lineContentRange[0]))) return;
9975
+ }
9976
+ if (!sourceCode.text.endsWith("|", lineContentRange[1])) return;
9977
+ let startIndex = lineContentRange[1] - 1;
9978
+ while (startIndex - 1 > lineContentRange[0] && isSpaceOrTab(sourceCode.text[startIndex - 1])) startIndex--;
9979
+ context.report({
9980
+ messageId: "unexpectedTrailingPipe",
9981
+ loc: {
9982
+ start: {
9983
+ line: lineLocation.end.line,
9984
+ column: lineLocation.end.column - (lineContentRange[1] - startIndex)
9985
+ },
9986
+ end: lineLocation.end
9987
+ },
9988
+ fix(fixer) {
9989
+ return fixer.removeRange([startIndex, lineContentRange[1]]);
9990
+ }
9991
+ });
9992
+ }
9993
+ }
9994
+ return { table(node) {
9995
+ verifyTablePipes(node);
9996
+ } };
9997
+ }
9998
+ });
9999
+
10000
+ //#endregion
10001
+ //#region src/rules/table-pipe-alignment.ts
10002
+ var table_pipe_alignment_default = createRule("table-pipe-alignment", {
10003
+ meta: {
10004
+ type: "layout",
10005
+ docs: {
10006
+ description: "enforce consistent alignment of table pipes",
10007
+ categories: ["standard"],
10008
+ listCategory: "Decorative"
10009
+ },
10010
+ fixable: "code",
10011
+ hasSuggestions: false,
10012
+ schema: [{
10013
+ type: "object",
10014
+ properties: { column: { enum: ["minimum", "consistent"] } },
10015
+ additionalProperties: false
10016
+ }],
10017
+ messages: {
10018
+ addSpaces: "Table pipe should be aligned at column {{expected}} (add {{count}} character{{plural}}).",
10019
+ removeSpaces: "Table pipe should be aligned at column {{expected}} (remove {{count}} character{{plural}})."
10020
+ }
10021
+ },
10022
+ create(context) {
10023
+ const sourceCode = context.sourceCode;
10024
+ const columnOption = (context.options[0] || {}).column || "minimum";
10025
+ /**
10026
+ * Verify the table pipes
10027
+ */
10028
+ function verifyTablePipes(rows) {
10029
+ let columnCount = 0;
10030
+ for (const row of rows) columnCount = Math.max(columnCount, row.cells.length);
10031
+ let targetRows = [...rows];
10032
+ for (let pipeIndex = 0; pipeIndex <= columnCount; pipeIndex++) {
10033
+ const expected = getExpectedPipePosition(rows, pipeIndex);
10034
+ if (expected == null) continue;
10035
+ const unreportedRows = [];
10036
+ for (const row of targetRows) if (verifyRowPipe(row, pipeIndex, expected)) unreportedRows.push(row);
10037
+ targetRows = unreportedRows;
10038
+ if (targetRows.length === 0) break;
10039
+ }
10040
+ }
10041
+ /**
10042
+ * Verify the pipe in the row
10043
+ */
10044
+ function verifyRowPipe(row, pipeIndex, expected) {
10045
+ let cellIndex;
10046
+ let pipe;
10047
+ if (pipeIndex === 0) {
10048
+ cellIndex = 0;
10049
+ pipe = "leadingPipe";
10050
+ } else {
10051
+ cellIndex = pipeIndex - 1;
10052
+ pipe = "trailingPipe";
10053
+ }
10054
+ if (row.cells.length <= cellIndex) return true;
10055
+ const cell = row.cells[cellIndex];
10056
+ const pipeToken = cell[pipe];
10057
+ if (!pipeToken) return true;
10058
+ return verifyPipe(pipeToken, expected, {
10059
+ cell,
10060
+ pipeIndex
10061
+ });
10062
+ }
10063
+ /**
10064
+ * Verify the pipe position
10065
+ */
10066
+ function verifyPipe(pipe, expected, ctx) {
10067
+ const actual = getTextWidth(sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1));
10068
+ const diff = expected - actual;
10069
+ if (diff === 0) return true;
10070
+ context.report({
10071
+ loc: pipe.loc,
10072
+ messageId: diff > 0 ? "addSpaces" : "removeSpaces",
10073
+ data: {
10074
+ expected: String(expected),
10075
+ count: String(Math.abs(diff)),
10076
+ plural: Math.abs(diff) === 1 ? "" : "s"
10077
+ },
10078
+ fix(fixer) {
10079
+ if (diff > 0) {
10080
+ if (ctx.pipeIndex === 0 || ctx.cell.type === "cell") return fixer.insertTextBeforeRange(pipe.range, " ".repeat(diff));
10081
+ return fixer.insertTextAfterRange([ctx.cell.delimiter.range[0], ctx.cell.delimiter.range[0] + 1], "-".repeat(diff));
10082
+ }
10083
+ const baseEdit = fixRemoveSpaces();
10084
+ if (baseEdit) return baseEdit;
10085
+ if (ctx.pipeIndex === 0 || ctx.cell.type === "cell") return null;
10086
+ const beforeDelimiter = sourceCode.lines[ctx.cell.delimiter.loc.start.line - 1].slice(0, ctx.cell.delimiter.loc.start.column - 1);
10087
+ const widthBeforeDelimiter = getTextWidth(beforeDelimiter);
10088
+ const newLength = expected - widthBeforeDelimiter;
10089
+ const minimumDelimiterLength = getMinimumDelimiterLength(ctx.cell.align);
10090
+ const spaceAfter = isNeedSpaceAfterContent(ctx.cell) ? " " : "";
10091
+ if (newLength < minimumDelimiterLength + spaceAfter.length) return null;
10092
+ const delimiterPrefix = ctx.cell.align === "left" || ctx.cell.align === "center" ? ":" : "";
10093
+ const delimiterSuffix = (ctx.cell.align === "right" || ctx.cell.align === "center" ? ":" : "") + spaceAfter;
10094
+ const newDelimiter = "-".repeat(newLength - delimiterPrefix.length - delimiterSuffix.length);
10095
+ return fixer.replaceTextRange([ctx.cell.delimiter.range[0], pipe.range[0]], delimiterPrefix + newDelimiter + delimiterSuffix);
10096
+ /**
10097
+ * Fixer to remove spaces before the pipe
10098
+ */
10099
+ function fixRemoveSpaces() {
10100
+ const beforePipe = sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1);
10101
+ const trimmedBeforePipe = beforePipe.trimEnd();
10102
+ const spacesBeforePipeLength = beforePipe.length - trimmedBeforePipe.length;
10103
+ const widthBeforePipe = getTextWidth(trimmedBeforePipe);
10104
+ const newSpacesLength = expected - widthBeforePipe;
10105
+ if (newSpacesLength < (ctx.pipeIndex > 0 && isNeedSpaceAfterContent(ctx.cell) ? 1 : 0)) return null;
10106
+ return fixer.replaceTextRange([pipe.range[0] - spacesBeforePipeLength, pipe.range[0]], " ".repeat(newSpacesLength));
10107
+ }
10108
+ }
10109
+ });
10110
+ return false;
10111
+ }
10112
+ /**
10113
+ * Get the expected pipe position for the index
10114
+ */
10115
+ function getExpectedPipePosition(rows, pipeIndex) {
10116
+ if (pipeIndex === 0) {
10117
+ const firstCell = rows[0].cells[0];
10118
+ const firstToken = firstCell.leadingPipe ?? firstCell.content;
10119
+ if (!firstToken) return null;
10120
+ return getTextWidth(sourceCode.lines[firstToken.loc.start.line - 1].slice(0, firstToken.loc.start.column - 1));
10121
+ }
10122
+ if (columnOption === "minimum") return getMinimumPipePosition(rows, pipeIndex);
10123
+ else if (columnOption === "consistent") {
10124
+ const columnIndex = pipeIndex - 1;
10125
+ for (const row of rows) {
10126
+ if (row.cells.length <= columnIndex) continue;
10127
+ const cell = row.cells[columnIndex];
10128
+ if (cell.type === "delimiter" || !cell.trailingPipe) continue;
10129
+ const width = getTextWidth(sourceCode.lines[cell.trailingPipe.loc.start.line - 1].slice(0, cell.trailingPipe.loc.start.column - 1));
10130
+ return Math.max(width, getMinimumPipePosition(rows, pipeIndex) || 0);
10131
+ }
10132
+ }
10133
+ return null;
10134
+ }
10135
+ /**
10136
+ * Get the minimum pipe position for the index
10137
+ */
10138
+ function getMinimumPipePosition(rows, pipeIndex) {
10139
+ let maxWidth = 0;
10140
+ const columnIndex = pipeIndex - 1;
10141
+ for (const row of rows) {
10142
+ if (row.cells.length <= columnIndex) continue;
10143
+ const cell = row.cells[columnIndex];
10144
+ let width;
10145
+ if (cell.type === "delimiter") {
10146
+ const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
10147
+ width = getTextWidth(sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1)) + minimumDelimiterLength;
10148
+ } else {
10149
+ if (!cell.content) continue;
10150
+ width = getTextWidth(sourceCode.lines[cell.content.loc.end.line - 1].slice(0, cell.content.loc.end.column - 1));
10151
+ }
10152
+ if (isNeedSpaceAfterContent(cell)) width += 1;
10153
+ maxWidth = Math.max(maxWidth, width);
10154
+ }
10155
+ return maxWidth;
10156
+ }
10157
+ /**
10158
+ * Get the minimum delimiter length based on alignment
10159
+ */
10160
+ function getMinimumDelimiterLength(align) {
10161
+ return align === "none" ? 1 : align === "center" ? 3 : 2;
10162
+ }
10163
+ /**
10164
+ * Check if a cell needs a space after its content
10165
+ */
10166
+ function isNeedSpaceAfterContent(cell) {
10167
+ let content;
10168
+ if (cell.type === "delimiter") content = cell.delimiter;
10169
+ else {
10170
+ if (!cell.content) return false;
10171
+ content = cell.content;
10172
+ }
10173
+ return cell.trailingPipe && content.range[1] < cell.trailingPipe.range[0];
10174
+ }
10175
+ /**
10176
+ * Convert a parsed table row to row data
10177
+ */
10178
+ function parsedTableRowToRowData(parsedRow) {
10179
+ return { cells: parsedRow.cells.map((cell, index) => {
10180
+ const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
10181
+ return {
10182
+ type: "cell",
10183
+ leadingPipe: cell.leadingPipe,
10184
+ content: cell.cell,
10185
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
10186
+ };
10187
+ }) };
10188
+ }
10189
+ /**
10190
+ * Convert a parsed table delimiter row to row data
10191
+ */
10192
+ function parsedTableDelimiterRowToRowData(parsedDelimiterRow) {
10193
+ return { cells: parsedDelimiterRow.delimiters.map((cell, index) => {
10194
+ const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
10195
+ return {
10196
+ type: "delimiter",
10197
+ leadingPipe: cell.leadingPipe,
10198
+ delimiter: cell.delimiter,
10199
+ align: cell.delimiter.align,
10200
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
10201
+ };
10202
+ }) };
10203
+ }
10204
+ return { table(node) {
10205
+ const parsed = parseTable(sourceCode, node);
10206
+ if (!parsed) return;
10207
+ const rows = [parsedTableRowToRowData(parsed.headerRow), parsedTableDelimiterRowToRowData(parsed.delimiterRow)];
10208
+ for (const bodyRow of parsed.bodyRows) rows.push(parsedTableRowToRowData(bodyRow));
10209
+ verifyTablePipes(rows);
10210
+ } };
10211
+ }
10212
+ });
10213
+
10214
+ //#endregion
10215
+ //#region src/rules/table-pipe-spacing.ts
10216
+ /**
10217
+ * Parsed options
10218
+ */
10219
+ function parseOptions(options) {
10220
+ const spaceOption = options?.space;
10221
+ const leadingSpace = (typeof spaceOption === "object" ? spaceOption.leading : spaceOption) || "always";
10222
+ const trailingSpace = (typeof spaceOption === "object" ? spaceOption.trailing : spaceOption) || "always";
10223
+ const cellAlignOption = options?.cellAlign;
10224
+ const defaultDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.defaultDelimiter : cellAlignOption) || "left";
10225
+ const leftAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.leftAlignmentDelimiter : cellAlignOption) || "left";
10226
+ const centerAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.centerAlignmentDelimiter : cellAlignOption) || "center";
10227
+ const rightAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.rightAlignmentDelimiter : cellAlignOption) || "right";
10228
+ return {
10229
+ leadingSpace,
10230
+ trailingSpace,
10231
+ cellAlignByDelimiter: {
10232
+ none: adjustAlign(defaultDelimiterCellAlign),
10233
+ left: adjustAlign(leftAlignmentDelimiterCellAlign),
10234
+ center: adjustAlign(centerAlignmentDelimiterCellAlign),
10235
+ right: adjustAlign(rightAlignmentDelimiterCellAlign)
10236
+ }
10237
+ };
10238
+ /**
10239
+ * Adjust the alignment option based on the spacing options.
10240
+ */
10241
+ function adjustAlign(align) {
10242
+ if (align === "left") {
10243
+ if (trailingSpace === "always") return "left";
10244
+ return "ignore";
10245
+ }
10246
+ if (align === "center") {
10247
+ if (leadingSpace === "always" && trailingSpace === "always") return "center";
10248
+ return "ignore";
10249
+ }
10250
+ if (align === "right") {
10251
+ if (leadingSpace === "always") return "right";
10252
+ return "ignore";
10253
+ }
10254
+ return align;
10255
+ }
10256
+ }
10257
+ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
10258
+ meta: {
10259
+ type: "layout",
10260
+ docs: {
10261
+ description: "enforce consistent spacing around table pipes",
10262
+ categories: ["standard"],
10263
+ listCategory: "Whitespace"
10264
+ },
10265
+ fixable: "whitespace",
10266
+ hasSuggestions: false,
10267
+ schema: [{
10268
+ type: "object",
10269
+ properties: {
10270
+ space: { anyOf: [{ enum: ["always", "never"] }, {
10271
+ type: "object",
10272
+ properties: {
10273
+ leading: { enum: ["always", "never"] },
10274
+ trailing: { enum: ["always", "never"] }
10275
+ },
10276
+ additionalProperties: false
10277
+ }] },
10278
+ cellAlign: { anyOf: [{ enum: [
10279
+ "left",
10280
+ "center",
10281
+ "right"
10282
+ ] }, {
10283
+ type: "object",
10284
+ properties: {
10285
+ defaultDelimiter: { enum: [
10286
+ "left",
10287
+ "center",
10288
+ "right",
10289
+ "ignore"
10290
+ ] },
10291
+ leftAlignmentDelimiter: { enum: [
10292
+ "left",
10293
+ "center",
10294
+ "right",
10295
+ "ignore"
10296
+ ] },
10297
+ centerAlignmentDelimiter: { enum: [
10298
+ "left",
10299
+ "center",
10300
+ "right",
10301
+ "ignore"
10302
+ ] },
10303
+ rightAlignmentDelimiter: { enum: [
10304
+ "left",
10305
+ "center",
10306
+ "right",
10307
+ "ignore"
10308
+ ] }
10309
+ },
10310
+ additionalProperties: false
10311
+ }] }
10312
+ },
10313
+ additionalProperties: false
10314
+ }],
10315
+ messages: {
10316
+ expectedSpaceBefore: "Expected 1 space before \"|\".",
10317
+ expectedNoSpaceBefore: "Expected no space before \"|\".",
10318
+ expectedSpaceAfter: "Expected 1 space after \"|\".",
10319
+ expectedNoSpaceAfter: "Expected no space after \"|\".",
10320
+ expectedAlignLeft: "Expected 1 space after \"|\" for left-aligned column.",
10321
+ expectedNoSpaceAlignLeft: "Expected no space after \"|\" for left-aligned column.",
10322
+ expectedAlignRight: "Expected 1 space before \"|\" for right-aligned column.",
10323
+ expectedNoSpaceAlignRight: "Expected no space before \"|\" for right-aligned column.",
10324
+ expectedAlignCenter: "Expected the number of spaces before and after the content to be the same or differ by 1 at most for center-aligned column."
10325
+ }
10326
+ },
10327
+ create(context) {
10328
+ const sourceCode = context.sourceCode;
10329
+ const options = parseOptions(context.options[0]);
10330
+ /**
10331
+ * Verify for the leading pipe.
10332
+ */
10333
+ function verifyLeadingPipe(pipe, nextToken) {
10334
+ if (options.leadingSpace === "always") {
10335
+ if (pipe.range[1] < nextToken.range[0]) return true;
10336
+ context.report({
10337
+ loc: pipe.loc,
10338
+ messageId: "expectedSpaceAfter",
10339
+ fix(fixer) {
10340
+ return fixer.insertTextAfterRange(pipe.range, " ");
10341
+ }
10342
+ });
10343
+ return false;
10344
+ } else if (options.leadingSpace === "never") {
10345
+ if (pipe.range[1] === nextToken.range[0]) return true;
10346
+ context.report({
10347
+ loc: {
10348
+ start: pipe.loc.end,
10349
+ end: nextToken.loc.start
10350
+ },
10351
+ messageId: "expectedNoSpaceAfter",
10352
+ fix(fixer) {
10353
+ return fixer.removeRange([pipe.range[1], nextToken.range[0]]);
10354
+ }
10355
+ });
10356
+ return false;
10357
+ }
10358
+ return true;
10359
+ }
10360
+ /**
10361
+ * Verify for the trailing pipe.
10362
+ */
10363
+ function verifyTrailingPipe(prevToken, pipe) {
10364
+ if (options.trailingSpace === "always") {
10365
+ if (prevToken.range[1] < pipe.range[0]) return true;
10366
+ context.report({
10367
+ loc: pipe.loc,
10368
+ messageId: "expectedSpaceBefore",
10369
+ fix(fixer) {
10370
+ return fixer.insertTextBeforeRange(pipe.range, " ");
10371
+ }
10372
+ });
10373
+ return false;
10374
+ } else if (options.trailingSpace === "never") {
10375
+ if (prevToken.range[1] === pipe.range[0]) return true;
10376
+ context.report({
10377
+ loc: {
10378
+ start: prevToken.loc.end,
10379
+ end: pipe.loc.start
10380
+ },
10381
+ messageId: "expectedNoSpaceBefore",
10382
+ fix(fixer) {
10383
+ return fixer.removeRange([prevToken.range[1], pipe.range[0]]);
10384
+ }
10385
+ });
10386
+ return false;
10387
+ }
10388
+ return true;
10389
+ }
10390
+ /**
10391
+ * Verify for the alignment of the pipe according to the delimiter alignment.
10392
+ * Return "leading" if the leading pipe is reported, "trailing" if the trailing pipe is reported, "both" if both are reported, or null if nothing is reported.
10393
+ */
10394
+ function verifyAlignPipe({ leadingPipe, content, trailingPipe }, cellAlign) {
10395
+ if (!leadingPipe || !trailingPipe || !content) return null;
10396
+ const lineText = sourceCode.lines[leadingPipe.loc.start.line - 1];
10397
+ if (cellAlign === "left") {
10398
+ const expectedWidth = options.leadingSpace === "always" ? 1 : 0;
10399
+ if (getLeadingSpacesWidth() === expectedWidth) return null;
10400
+ context.report({
10401
+ loc: leadingPipe.range[1] < content.range[0] ? {
10402
+ start: leadingPipe.loc.end,
10403
+ end: content.loc.start
10404
+ } : leadingPipe.loc,
10405
+ messageId: expectedWidth >= 1 ? "expectedAlignLeft" : "expectedNoSpaceAlignLeft",
10406
+ *fix(fixer) {
10407
+ const cellWidth = getCellWidth();
10408
+ const contentTextWidth = getContentTextWidth();
10409
+ const newLeadingSpaces = " ".repeat(expectedWidth);
10410
+ const newTrailingSpaces = " ".repeat(Math.max(cellWidth - contentTextWidth - expectedWidth, 0));
10411
+ const contentText = getNormalizedContentText();
10412
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10413
+ }
10414
+ });
10415
+ return "leading";
10416
+ } else if (cellAlign === "right") {
10417
+ const expectedWidth = options.trailingSpace === "always" ? 1 : 0;
10418
+ if (getTrailingSpacesWidth() === expectedWidth) return null;
10419
+ context.report({
10420
+ loc: content.range[1] < trailingPipe.range[0] ? {
10421
+ start: content.loc.end,
10422
+ end: trailingPipe.loc.start
10423
+ } : trailingPipe.loc,
10424
+ messageId: expectedWidth >= 1 ? "expectedAlignRight" : "expectedNoSpaceAlignRight",
10425
+ *fix(fixer) {
10426
+ const cellWidth = getCellWidth();
10427
+ const contentTextWidth = getContentTextWidth();
10428
+ const newLeadingSpaces = " ".repeat(Math.max(cellWidth - contentTextWidth - expectedWidth, 0));
10429
+ const newTrailingSpaces = " ".repeat(expectedWidth);
10430
+ const contentText = getNormalizedContentText();
10431
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10432
+ }
10433
+ });
10434
+ return "trailing";
10435
+ } else if (cellAlign === "center") {
10436
+ const leadingSpacesWidth = getLeadingSpacesWidth();
10437
+ const trailingSpacesWidth = getTrailingSpacesWidth();
10438
+ if (leadingSpacesWidth === trailingSpacesWidth || leadingSpacesWidth + 1 === trailingSpacesWidth) return null;
10439
+ const leadingReportLoc = leadingPipe.range[1] < content.range[0] ? {
10440
+ start: leadingPipe.loc.end,
10441
+ end: content.loc.start
10442
+ } : leadingPipe.loc;
10443
+ const trailingReportLoc = content.range[1] < trailingPipe.range[0] ? {
10444
+ start: content.loc.end,
10445
+ end: trailingPipe.loc.start
10446
+ } : trailingPipe.loc;
10447
+ for (const reportLoc of [leadingReportLoc, trailingReportLoc]) context.report({
10448
+ loc: reportLoc,
10449
+ messageId: "expectedAlignCenter",
10450
+ *fix(fixer) {
10451
+ const cellWidth = getCellWidth();
10452
+ const contentTextWidth = getContentTextWidth();
10453
+ const spacesLength = cellWidth - contentTextWidth;
10454
+ const leadingSpacesLength = Math.floor(spacesLength / 2);
10455
+ const trailingSpacesLength = spacesLength - leadingSpacesLength;
10456
+ const newLeadingSpaces = " ".repeat(leadingSpacesLength);
10457
+ const newTrailingSpaces = " ".repeat(trailingSpacesLength);
10458
+ const contentText = getNormalizedContentText();
10459
+ yield fixer.replaceTextRange([leadingPipe.range[1], trailingPipe.range[0]], `${newLeadingSpaces}${contentText}${newTrailingSpaces}`);
10460
+ }
10461
+ });
10462
+ return "both";
10463
+ }
10464
+ return null;
10465
+ /**
10466
+ * Get the width of the leading spaces in the cell.
10467
+ */
10468
+ function getLeadingSpacesWidth() {
10469
+ return getTextWidth(lineText, leadingPipe.loc.end.column - 1, content.loc.start.column - 1);
10470
+ }
10471
+ /**
10472
+ * Get the width of the trailing spaces in the cell.
10473
+ */
10474
+ function getTrailingSpacesWidth() {
10475
+ return getTextWidth(lineText, content.loc.end.column - 1, trailingPipe.loc.start.column - 1);
10476
+ }
10477
+ /**
10478
+ * Get the width of the whole cell (including leading and trailing spaces)
10479
+ */
10480
+ function getCellWidth() {
10481
+ return getTextWidth(lineText, leadingPipe.loc.end.column - 1, trailingPipe.loc.start.column - 1);
10482
+ }
10483
+ /**
10484
+ * Get the width of the content text (excluding leading and trailing spaces)
10485
+ */
10486
+ function getContentTextWidth() {
10487
+ return getTextWidth(lineText, content.loc.start.column - 1, content.loc.end.column - 1);
10488
+ }
10489
+ /**
10490
+ * Get the normalized content text (with normalized spaces)
10491
+ */
10492
+ function getNormalizedContentText() {
10493
+ const prefixWidth = getWidth(lineText.slice(0, content.loc.start.column - 1));
10494
+ let result = "";
10495
+ for (const c of lineText.slice(content.loc.start.column - 1, content.loc.end.column - 1)) if (c === " ") result += " ".repeat(4 - (prefixWidth + result.length) % 4);
10496
+ else result += c;
10497
+ return result;
10498
+ }
10499
+ }
10500
+ /**
10501
+ * Convert a parsed table row to cell data list
10502
+ */
10503
+ function parsedTableRowToCellDataList(parsedRow) {
10504
+ return parsedRow.cells.map((cell, index) => {
10505
+ const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
10506
+ return {
10507
+ type: "cell",
10508
+ leadingPipe: cell.leadingPipe,
10509
+ content: cell.cell,
10510
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
10511
+ };
10512
+ });
10513
+ }
10514
+ /**
10515
+ * Convert a parsed table delimiter row to delimiter data list
10516
+ */
10517
+ function parsedTableDelimiterRowToDelimiterDataList(parsedDelimiterRow) {
10518
+ return parsedDelimiterRow.delimiters.map((cell, index) => {
10519
+ const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
10520
+ return {
10521
+ type: "delimiter",
10522
+ leadingPipe: cell.leadingPipe,
10523
+ content: cell.delimiter,
10524
+ align: cell.delimiter.align,
10525
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
10526
+ };
10527
+ });
10528
+ }
10529
+ return { table(node) {
10530
+ const parsedDelimiterRow = parseTableDelimiterRow(sourceCode, node);
10531
+ const delimiters = parsedDelimiterRow && parsedTableDelimiterRowToDelimiterDataList(parsedDelimiterRow);
10532
+ for (const row of node.children) {
10533
+ const parsedRow = parseTableRow(sourceCode, row);
10534
+ if (!parsedRow) continue;
10535
+ const cells = parsedTableRowToCellDataList(parsedRow);
10536
+ for (let columnIndex = 0; columnIndex < cells.length; columnIndex++) {
10537
+ const cell = cells[columnIndex];
10538
+ const delimiter = delimiters && columnIndex < delimiters.length ? delimiters[columnIndex] : null;
10539
+ const alignReportedPoint = delimiter ? verifyAlignPipe(cell, options.cellAlignByDelimiter[delimiter.align]) : null;
10540
+ if (alignReportedPoint === "both") continue;
10541
+ if (cell.leadingPipe && alignReportedPoint !== "leading") {
10542
+ if (options.leadingSpace !== "never" || cell.content) {
10543
+ const nextToken = getNextToken(cells, columnIndex);
10544
+ if (nextToken) verifyLeadingPipe(cell.leadingPipe, nextToken);
10545
+ }
10546
+ }
10547
+ if (cell.trailingPipe && options.trailingSpace !== "never" && alignReportedPoint !== "trailing") {
10548
+ const prevToken = getPrevToken(cells, columnIndex);
10549
+ if (prevToken) verifyTrailingPipe(prevToken, cell.trailingPipe);
10550
+ }
10551
+ }
10552
+ }
10553
+ if (!delimiters) return;
10554
+ for (let columnIndex = 0; columnIndex < delimiters.length; columnIndex++) {
10555
+ const delimiter = delimiters[columnIndex];
10556
+ const alignReportedPoint = verifyAlignPipe(delimiter, options.cellAlignByDelimiter[delimiter.align]);
10557
+ if (alignReportedPoint === "both") continue;
10558
+ if (delimiter.leadingPipe && alignReportedPoint !== "leading") verifyLeadingPipe(delimiter.leadingPipe, delimiter.content);
10559
+ if (delimiter.trailingPipe && alignReportedPoint !== "trailing") verifyTrailingPipe(delimiter.content, delimiter.trailingPipe);
10560
+ }
10561
+ } };
10562
+ /**
10563
+ * Get the next token (pipe or cell) after the given column index.
10564
+ */
10565
+ function getNextToken(cells, columnIndex) {
10566
+ for (let i = columnIndex; i < cells.length; i++) {
10567
+ const cell = cells[i];
10568
+ const token = cell.content ?? cell.trailingPipe;
10569
+ if (token) return token;
10570
+ }
10571
+ return null;
10572
+ }
10573
+ /**
10574
+ * Get the prev token (pipe or cell) after the given column index.
10575
+ */
10576
+ function getPrevToken(cells, columnIndex) {
10577
+ for (let i = columnIndex; i >= 0; i--) {
10578
+ const cell = cells[i];
10579
+ const token = cell.content ?? cell.leadingPipe;
10580
+ if (token) return token;
10581
+ }
10582
+ return null;
10583
+ }
10584
+ }
10585
+ });
10586
+
9658
10587
  //#endregion
9659
10588
  //#region src/rules/thematic-break-character-style.ts
9660
10589
  var thematic_break_character_style_default = createRule("thematic-break-character-style", {
@@ -9663,7 +10592,7 @@ var thematic_break_character_style_default = createRule("thematic-break-characte
9663
10592
  docs: {
9664
10593
  description: "enforce consistent character style for thematic breaks (horizontal rules) in Markdown.",
9665
10594
  categories: ["standard"],
9666
- listCategory: "Stylistic"
10595
+ listCategory: "Notation"
9667
10596
  },
9668
10597
  fixable: "code",
9669
10598
  hasSuggestions: false,
@@ -9742,7 +10671,7 @@ var thematic_break_length_default = createRule("thematic-break-length", {
9742
10671
  docs: {
9743
10672
  description: "enforce consistent length for thematic breaks (horizontal rules) in Markdown.",
9744
10673
  categories: ["standard"],
9745
- listCategory: "Stylistic"
10674
+ listCategory: "Decorative"
9746
10675
  },
9747
10676
  fixable: "code",
9748
10677
  hasSuggestions: false,
@@ -9808,7 +10737,7 @@ var thematic_break_sequence_pattern_default = createRule("thematic-break-sequenc
9808
10737
  docs: {
9809
10738
  description: "enforce consistent repeating patterns for thematic breaks (horizontal rules) in Markdown.",
9810
10739
  categories: ["standard"],
9811
- listCategory: "Stylistic"
10740
+ listCategory: "Decorative"
9812
10741
  },
9813
10742
  fixable: "code",
9814
10743
  hasSuggestions: false,
@@ -9905,6 +10834,9 @@ const rules$1 = [
9905
10834
  sort_definitions_default,
9906
10835
  strikethrough_delimiters_style_default,
9907
10836
  table_header_casing_default,
10837
+ table_leading_trailing_pipes_default,
10838
+ table_pipe_alignment_default,
10839
+ table_pipe_spacing_default,
9908
10840
  thematic_break_character_style_default,
9909
10841
  thematic_break_length_default,
9910
10842
  thematic_break_sequence_pattern_default
@@ -9993,6 +10925,9 @@ const rules$2 = {
9993
10925
  "markdown-preferences/setext-heading-underline-length": "error",
9994
10926
  "markdown-preferences/sort-definitions": "error",
9995
10927
  "markdown-preferences/strikethrough-delimiters-style": "error",
10928
+ "markdown-preferences/table-leading-trailing-pipes": "error",
10929
+ "markdown-preferences/table-pipe-alignment": "error",
10930
+ "markdown-preferences/table-pipe-spacing": "error",
9996
10931
  "markdown-preferences/thematic-break-character-style": "error",
9997
10932
  "markdown-preferences/thematic-break-length": "error",
9998
10933
  "markdown-preferences/thematic-break-sequence-pattern": "error"
@@ -10005,7 +10940,7 @@ var meta_exports = __export({
10005
10940
  version: () => version
10006
10941
  });
10007
10942
  const name = "eslint-plugin-markdown-preferences";
10008
- const version = "0.24.0";
10943
+ const version = "0.25.0";
10009
10944
 
10010
10945
  //#endregion
10011
10946
  //#region src/index.ts