eslint-plugin-markdown-preferences 0.29.0 β†’ 0.30.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/README.md CHANGED
@@ -13,17 +13,15 @@ A specialized ESLint plugin that helps enforce consistent writing style and form
13
13
 
14
14
  ## πŸ“› Features
15
15
 
16
- - πŸ“ **Comprehensive style enforcement**\
17
- Unifies document expression and description style: heading casing, table header casing, inline code/link usage, emoji notation, and more.
18
- - 🧩 **Notation and formatting consistency**\
19
- Standardizes Markdown notation: list markers, code fences, link/reference style, thematic breaks, and table formatting.
20
- - 🎨 **Whitespace and decorative rules**\
21
- Controls indentation, spacing, line breaks, trailing spaces, and decorative elements for clean, readable Markdown.
22
- - πŸ”§ **Auto-fix support**\
23
- Most rules support ESLint's `--fix` option for effortless formatting and correction.
24
- - βš™οΈ **Flexible configuration**\
25
- Provides both "recommended" and "standard" configs, and allows you to finely customize formatting and rules to suit your preferences and Markdown style.
26
- - 🌐 **Live demo & documentation**\
16
+ - πŸ“ **Comprehensive style enforcement**
17
+ Enforces consistent heading casing, table header casing, inline code/link usage, emoji notation, and more for unified document style.
18
+ - 🧩 **Powerful formatting consistency**
19
+ Strongly standardizes Markdown formatting: whitespace, indentation, spacing, line breaks, list markers, code fences, links, references, thematic breaks, tables, and decorative elementsβ€”ensuring documents are clean and uniform.
20
+ - πŸš€ **Extended Markdown syntax support**
21
+ Supports custom containers, mathematical expressions, and other extended syntax for high compatibility with VitePress.
22
+ - πŸ”§ **Auto-fix support**
23
+ Most rules support ESLint's `--fix` option for automatic correction.
24
+ - 🌐 **Online Demo & Documentation**
27
25
  Try it instantly in the [Online Demo](https://eslint-online-playground.netlify.app/#eslint-plugin-markdown-preferences) and see [full documentation][documentation site].
28
26
 
29
27
  <!--DOCS_IGNORE_START-->
@@ -192,21 +190,23 @@ The rules with the following πŸ’„ are included in the `standard` config.
192
190
 
193
191
  <!-- prettier-ignore-start -->
194
192
 
195
- | Rule ID | Description | Fixable | Config |
196
- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------- | :-----: | :----: |
197
- | [markdown-preferences/blockquote-marker-alignment](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/blockquote-marker-alignment.html) | enforce consistent alignment of blockquote markers | πŸ”§ | β­πŸ’„ |
198
- | [markdown-preferences/indent](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/indent.html) | enforce consistent indentation in Markdown files | πŸ”§ | πŸ’„ |
199
- | [markdown-preferences/link-bracket-newline](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-bracket-newline.html) | enforce linebreaks after opening and before closing link brackets | πŸ”§ | πŸ’„ |
200
- | [markdown-preferences/link-bracket-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-bracket-spacing.html) | enforce consistent spacing inside link brackets | πŸ”§ | πŸ’„ |
201
- | [markdown-preferences/link-paren-newline](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-paren-newline.html) | enforce linebreaks after opening and before closing link parentheses | πŸ”§ | πŸ’„ |
202
- | [markdown-preferences/link-paren-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-paren-spacing.html) | enforce consistent spacing inside link parentheses | πŸ”§ | πŸ’„ |
203
- | [markdown-preferences/list-marker-alignment](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html) | enforce consistent alignment of list markers | πŸ”§ | β­πŸ’„ |
204
- | [markdown-preferences/no-multi-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multi-spaces.html) | disallow multiple spaces | πŸ”§ | πŸ’„ |
205
- | [markdown-preferences/no-multiple-empty-lines](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multiple-empty-lines.html) | disallow multiple empty lines in Markdown files. | πŸ”§ | πŸ’„ |
206
- | [markdown-preferences/no-trailing-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-trailing-spaces.html) | disallow trailing whitespace at the end of lines in Markdown files. | πŸ”§ | πŸ’„ |
207
- | [markdown-preferences/padded-custom-containers](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padded-custom-containers.html) | disallow or require padding inside custom containers | πŸ”§ | πŸ’„ |
208
- | [markdown-preferences/padding-line-between-blocks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padding-line-between-blocks.html) | require or disallow padding lines between blocks | πŸ”§ | πŸ’„ |
209
- | [markdown-preferences/table-pipe-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/table-pipe-spacing.html) | enforce consistent spacing around table pipes | πŸ”§ | πŸ’„ |
193
+ | Rule ID | Description | Fixable | Config |
194
+ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------- | :-----: | :----: |
195
+ | [markdown-preferences/blockquote-marker-alignment](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/blockquote-marker-alignment.html) | enforce consistent alignment of blockquote markers | πŸ”§ | β­πŸ’„ |
196
+ | [markdown-preferences/code-fence-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-spacing.html) | require or disallow spacing between opening code fence and language identifier | πŸ”§ | |
197
+ | [markdown-preferences/custom-container-marker-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/custom-container-marker-spacing.html) | require or disallow spacing between opening custom container marker and info | πŸ”§ | |
198
+ | [markdown-preferences/indent](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/indent.html) | enforce consistent indentation in Markdown files | πŸ”§ | πŸ’„ |
199
+ | [markdown-preferences/link-bracket-newline](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-bracket-newline.html) | enforce linebreaks after opening and before closing link brackets | πŸ”§ | πŸ’„ |
200
+ | [markdown-preferences/link-bracket-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-bracket-spacing.html) | enforce consistent spacing inside link brackets | πŸ”§ | πŸ’„ |
201
+ | [markdown-preferences/link-paren-newline](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-paren-newline.html) | enforce linebreaks after opening and before closing link parentheses | πŸ”§ | πŸ’„ |
202
+ | [markdown-preferences/link-paren-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/link-paren-spacing.html) | enforce consistent spacing inside link parentheses | πŸ”§ | πŸ’„ |
203
+ | [markdown-preferences/list-marker-alignment](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html) | enforce consistent alignment of list markers | πŸ”§ | β­πŸ’„ |
204
+ | [markdown-preferences/no-multi-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multi-spaces.html) | disallow multiple spaces | πŸ”§ | πŸ’„ |
205
+ | [markdown-preferences/no-multiple-empty-lines](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-multiple-empty-lines.html) | disallow multiple empty lines in Markdown files. | πŸ”§ | πŸ’„ |
206
+ | [markdown-preferences/no-trailing-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-trailing-spaces.html) | disallow trailing whitespace at the end of lines in Markdown files. | πŸ”§ | πŸ’„ |
207
+ | [markdown-preferences/padded-custom-containers](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padded-custom-containers.html) | disallow or require padding inside custom containers | πŸ”§ | πŸ’„ |
208
+ | [markdown-preferences/padding-line-between-blocks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padding-line-between-blocks.html) | require or disallow padding lines between blocks | πŸ”§ | πŸ’„ |
209
+ | [markdown-preferences/table-pipe-spacing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/table-pipe-spacing.html) | enforce consistent spacing around table pipes | πŸ”§ | πŸ’„ |
210
210
 
211
211
  <!-- prettier-ignore-end -->
212
212
 
package/lib/index.d.ts CHANGED
@@ -45,11 +45,21 @@ interface RuleOptions {
45
45
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-length.html
46
46
  */
47
47
  'markdown-preferences/code-fence-length'?: Linter.RuleEntry<MarkdownPreferencesCodeFenceLength>;
48
+ /**
49
+ * require or disallow spacing between opening code fence and language identifier
50
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-spacing.html
51
+ */
52
+ 'markdown-preferences/code-fence-spacing'?: Linter.RuleEntry<MarkdownPreferencesCodeFenceSpacing>;
48
53
  /**
49
54
  * enforce a consistent code fence style (backtick or tilde) in Markdown fenced code blocks.
50
55
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-style.html
51
56
  */
52
57
  'markdown-preferences/code-fence-style'?: Linter.RuleEntry<MarkdownPreferencesCodeFenceStyle>;
58
+ /**
59
+ * require or disallow spacing between opening custom container marker and info
60
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/custom-container-marker-spacing.html
61
+ */
62
+ 'markdown-preferences/custom-container-marker-spacing'?: Linter.RuleEntry<MarkdownPreferencesCustomContainerMarkerSpacing>;
53
63
  /**
54
64
  * require link definitions and footnote definitions to be placed at the end of the document
55
65
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/definitions-last.html
@@ -287,9 +297,15 @@ type MarkdownPreferencesCodeFenceLength = [] | [{
287
297
  fallbackLength?: (number | ("minimum" | "as-is"));
288
298
  }[];
289
299
  }];
300
+ type MarkdownPreferencesCodeFenceSpacing = [] | [{
301
+ space?: ("always" | "never");
302
+ }];
290
303
  type MarkdownPreferencesCodeFenceStyle = [] | [{
291
304
  style?: ("backtick" | "tilde");
292
305
  }];
306
+ type MarkdownPreferencesCustomContainerMarkerSpacing = [] | [{
307
+ space?: ("always" | "never");
308
+ }];
293
309
  type MarkdownPreferencesDefinitionsLast = [] | [{
294
310
  linkDefinitionPlacement?: {
295
311
  referencedFromSingleSection?: ("document-last" | "section-last");
@@ -516,7 +532,7 @@ declare namespace meta_d_exports {
516
532
  export { name, version };
517
533
  }
518
534
  declare const name: "eslint-plugin-markdown-preferences";
519
- declare const version: "0.29.0";
535
+ declare const version: "0.30.0";
520
536
  //#endregion
521
537
  //#region src/language/ast-types.d.ts
522
538
  type Node = mdast.Node;
package/lib/index.js CHANGED
@@ -1279,6 +1279,62 @@ var code_fence_length_default = createRule("code-fence-length", {
1279
1279
  }
1280
1280
  });
1281
1281
 
1282
+ //#endregion
1283
+ //#region src/rules/code-fence-spacing.ts
1284
+ var code_fence_spacing_default = createRule("code-fence-spacing", {
1285
+ meta: {
1286
+ type: "layout",
1287
+ docs: {
1288
+ description: "require or disallow spacing between opening code fence and language identifier",
1289
+ categories: [],
1290
+ listCategory: "Whitespace"
1291
+ },
1292
+ fixable: "whitespace",
1293
+ hasSuggestions: false,
1294
+ schema: [{
1295
+ type: "object",
1296
+ properties: { space: { enum: ["always", "never"] } },
1297
+ additionalProperties: false
1298
+ }],
1299
+ messages: {
1300
+ expectedSpace: "Expected a space between code fence and language identifier.",
1301
+ unexpectedSpace: "Unexpected space between code fence and language identifier."
1302
+ }
1303
+ },
1304
+ create(context) {
1305
+ const sourceCode = context.sourceCode;
1306
+ const space = (context.options[0] || {}).space || "never";
1307
+ return { code(node) {
1308
+ const parsed = parseFencedCodeBlock(sourceCode, node);
1309
+ if (!parsed) return;
1310
+ const { openingFence, language: language$2 } = parsed;
1311
+ if (!language$2) return;
1312
+ const hasSpace = openingFence.range[1] < language$2.range[0];
1313
+ if (space === "always") {
1314
+ if (hasSpace) return;
1315
+ context.report({
1316
+ node,
1317
+ loc: getSourceLocationFromRange(sourceCode, node, language$2.range),
1318
+ messageId: "expectedSpace",
1319
+ fix(fixer) {
1320
+ return fixer.insertTextAfterRange(openingFence.range, " ");
1321
+ }
1322
+ });
1323
+ } else if (space === "never") {
1324
+ if (!hasSpace) return;
1325
+ context.report({
1326
+ node,
1327
+ loc: getSourceLocationFromRange(sourceCode, node, [openingFence.range[1], language$2.range[0]]),
1328
+ messageId: "unexpectedSpace",
1329
+ fix(fixer) {
1330
+ return fixer.removeRange([openingFence.range[1], language$2.range[0]]);
1331
+ }
1332
+ });
1333
+ }
1334
+ } };
1335
+ }
1336
+ });
1337
+
1282
1338
  //#endregion
1283
1339
  //#region src/rules/code-fence-style.ts
1284
1340
  var code_fence_style_default = createRule("code-fence-style", {
@@ -1327,6 +1383,127 @@ var code_fence_style_default = createRule("code-fence-style", {
1327
1383
  }
1328
1384
  });
1329
1385
 
1386
+ //#endregion
1387
+ //#region src/utils/custom-container.ts
1388
+ const RE_OPENING_SEQUENCE = /^(:{3,})/u;
1389
+ /**
1390
+ * Parse the custom container.
1391
+ */
1392
+ function parseCustomContainer(sourceCode, node) {
1393
+ const loc = sourceCode.getLoc(node);
1394
+ const range = sourceCode.getRange(node);
1395
+ const text = sourceCode.text.slice(...range);
1396
+ const match = RE_OPENING_SEQUENCE.exec(text);
1397
+ if (!match) return null;
1398
+ const [, sequenceText] = match;
1399
+ const afterOpeningSequence = sourceCode.lines[loc.start.line - 1].slice(loc.start.column - 1 + sequenceText.length);
1400
+ const trimmedAfterOpeningSequence = afterOpeningSequence.trimStart();
1401
+ const spaceAfterOpeningSequenceLength = afterOpeningSequence.length - trimmedAfterOpeningSequence.length;
1402
+ const infoText = trimmedAfterOpeningSequence.trimEnd();
1403
+ if (!infoText) return null;
1404
+ const openingSequenceRange = [range[0], range[0] + sequenceText.length];
1405
+ const openingSequence = {
1406
+ text: sequenceText,
1407
+ range: openingSequenceRange,
1408
+ loc: getSourceLocationFromRange(sourceCode, node, openingSequenceRange)
1409
+ };
1410
+ const infoRange = [openingSequence.range[1] + spaceAfterOpeningSequenceLength, openingSequence.range[1] + spaceAfterOpeningSequenceLength + infoText.length];
1411
+ const info = {
1412
+ text: infoText,
1413
+ range: infoRange,
1414
+ loc: getSourceLocationFromRange(sourceCode, node, infoRange)
1415
+ };
1416
+ const sequenceChar = sequenceText[0];
1417
+ let closingSequenceText = "";
1418
+ const trimmed = text.trimEnd();
1419
+ const trailingSpacesLength = text.length - trimmed.length;
1420
+ for (let index = trimmed.length - 1; index >= 0; index--) {
1421
+ const c = trimmed[index];
1422
+ if (c === sequenceChar || isSpaceOrTab(c)) {
1423
+ closingSequenceText = c + closingSequenceText;
1424
+ continue;
1425
+ }
1426
+ if (c === ">") {
1427
+ closingSequenceText = ` ${closingSequenceText}`;
1428
+ continue;
1429
+ }
1430
+ if (c === "\n") break;
1431
+ closingSequenceText = "";
1432
+ break;
1433
+ }
1434
+ closingSequenceText = closingSequenceText.trimStart();
1435
+ if (!closingSequenceText || !closingSequenceText.startsWith(sequenceText)) return {
1436
+ openingSequence,
1437
+ info,
1438
+ closingSequence: null
1439
+ };
1440
+ const closingSequenceRange = [range[1] - trailingSpacesLength - closingSequenceText.length, range[1] - trailingSpacesLength];
1441
+ return {
1442
+ openingSequence,
1443
+ info,
1444
+ closingSequence: {
1445
+ text: closingSequenceText,
1446
+ range: closingSequenceRange,
1447
+ loc: getSourceLocationFromRange(sourceCode, node, closingSequenceRange)
1448
+ }
1449
+ };
1450
+ }
1451
+
1452
+ //#endregion
1453
+ //#region src/rules/custom-container-marker-spacing.ts
1454
+ var custom_container_marker_spacing_default = createRule("custom-container-marker-spacing", {
1455
+ meta: {
1456
+ type: "layout",
1457
+ docs: {
1458
+ description: "require or disallow spacing between opening custom container marker and info",
1459
+ categories: [],
1460
+ listCategory: "Whitespace"
1461
+ },
1462
+ fixable: "whitespace",
1463
+ hasSuggestions: false,
1464
+ schema: [{
1465
+ type: "object",
1466
+ properties: { space: { enum: ["always", "never"] } },
1467
+ additionalProperties: false
1468
+ }],
1469
+ messages: {
1470
+ expectedSpace: "Expected a space between opening custom container marker and info.",
1471
+ unexpectedSpace: "Unexpected space between opening custom container marker and info."
1472
+ }
1473
+ },
1474
+ create(context) {
1475
+ const sourceCode = context.sourceCode;
1476
+ const space = (context.options[0] || {}).space || "always";
1477
+ return { customContainer(node) {
1478
+ const parsed = parseCustomContainer(sourceCode, node);
1479
+ if (!parsed) return;
1480
+ const { openingSequence, info } = parsed;
1481
+ const hasSpace = openingSequence.range[1] < info.range[0];
1482
+ if (space === "always") {
1483
+ if (hasSpace) return;
1484
+ context.report({
1485
+ node,
1486
+ loc: getSourceLocationFromRange(sourceCode, node, info.range),
1487
+ messageId: "expectedSpace",
1488
+ fix(fixer) {
1489
+ return fixer.insertTextAfterRange(openingSequence.range, " ");
1490
+ }
1491
+ });
1492
+ } else if (space === "never") {
1493
+ if (!hasSpace) return;
1494
+ context.report({
1495
+ node,
1496
+ loc: getSourceLocationFromRange(sourceCode, node, [openingSequence.range[1], info.range[0]]),
1497
+ messageId: "unexpectedSpace",
1498
+ fix(fixer) {
1499
+ return fixer.removeRange([openingSequence.range[1], info.range[0]]);
1500
+ }
1501
+ });
1502
+ }
1503
+ } };
1504
+ }
1505
+ });
1506
+
1330
1507
  //#endregion
1331
1508
  //#region src/rules/definitions-last.ts
1332
1509
  /**
@@ -7545,72 +7722,6 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
7545
7722
  }
7546
7723
  });
7547
7724
 
7548
- //#endregion
7549
- //#region src/utils/custom-container.ts
7550
- const RE_OPENING_SEQUENCE = /^(:{3,})/u;
7551
- /**
7552
- * Parse the custom container.
7553
- */
7554
- function parseCustomContainer(sourceCode, node) {
7555
- const loc = sourceCode.getLoc(node);
7556
- const range = sourceCode.getRange(node);
7557
- const text = sourceCode.text.slice(...range);
7558
- const match = RE_OPENING_SEQUENCE.exec(text);
7559
- if (!match) return null;
7560
- const [, sequenceText] = match;
7561
- const afterOpeningSequence = sourceCode.lines[loc.start.line - 1].slice(loc.start.column - 1 + sequenceText.length);
7562
- const trimmedAfterOpeningSequence = afterOpeningSequence.trimStart();
7563
- const spaceAfterOpeningSequenceLength = afterOpeningSequence.length - trimmedAfterOpeningSequence.length;
7564
- const infoText = trimmedAfterOpeningSequence.trimEnd();
7565
- if (!infoText) return null;
7566
- const openingSequenceRange = [range[0], range[0] + sequenceText.length];
7567
- const openingSequence = {
7568
- text: sequenceText,
7569
- range: openingSequenceRange,
7570
- loc: getSourceLocationFromRange(sourceCode, node, openingSequenceRange)
7571
- };
7572
- const infoRange = [openingSequence.range[1] + spaceAfterOpeningSequenceLength, openingSequence.range[1] + spaceAfterOpeningSequenceLength + infoText.length];
7573
- const info = {
7574
- text: infoText,
7575
- range: infoRange,
7576
- loc: getSourceLocationFromRange(sourceCode, node, infoRange)
7577
- };
7578
- const sequenceChar = sequenceText[0];
7579
- let closingSequenceText = "";
7580
- const trimmed = text.trimEnd();
7581
- const trailingSpacesLength = text.length - trimmed.length;
7582
- for (let index = trimmed.length - 1; index >= 0; index--) {
7583
- const c = trimmed[index];
7584
- if (c === sequenceChar || isSpaceOrTab(c)) {
7585
- closingSequenceText = c + closingSequenceText;
7586
- continue;
7587
- }
7588
- if (c === ">") {
7589
- closingSequenceText = ` ${closingSequenceText}`;
7590
- continue;
7591
- }
7592
- if (c === "\n") break;
7593
- closingSequenceText = "";
7594
- break;
7595
- }
7596
- closingSequenceText = closingSequenceText.trimStart();
7597
- if (!closingSequenceText || !closingSequenceText.startsWith(sequenceText)) return {
7598
- openingSequence,
7599
- info,
7600
- closingSequence: null
7601
- };
7602
- const closingSequenceRange = [range[1] - trailingSpacesLength - closingSequenceText.length, range[1] - trailingSpacesLength];
7603
- return {
7604
- openingSequence,
7605
- info,
7606
- closingSequence: {
7607
- text: closingSequenceText,
7608
- range: closingSequenceRange,
7609
- loc: getSourceLocationFromRange(sourceCode, node, closingSequenceRange)
7610
- }
7611
- };
7612
- }
7613
-
7614
7725
  //#endregion
7615
7726
  //#region src/rules/no-implicit-block-closing.ts
7616
7727
  var no_implicit_block_closing_default = createRule("no-implicit-block-closing", {
@@ -10696,302 +10807,61 @@ var table_leading_trailing_pipes_default = createRule("table-leading-trailing-pi
10696
10807
  });
10697
10808
 
10698
10809
  //#endregion
10699
- //#region src/rules/table-pipe-alignment.ts
10700
- var table_pipe_alignment_default = createRule("table-pipe-alignment", {
10810
+ //#region src/rules/table-pipe-spacing.ts
10811
+ const currentOption = /* @__PURE__ */ new WeakMap();
10812
+ /**
10813
+ * Get the current options for the given source code.
10814
+ * This is a method that allows you to access the configuration of this rule from another rule.
10815
+ */
10816
+ function getCurrentTablePipeSpacingOption(sourceCode) {
10817
+ return currentOption.get(sourceCode) ?? null;
10818
+ }
10819
+ /**
10820
+ * Parsed options
10821
+ */
10822
+ function parseOptions(options) {
10823
+ const spaceOption = options?.space;
10824
+ const leadingSpace = (typeof spaceOption === "object" ? spaceOption.leading : spaceOption) || "always";
10825
+ const trailingSpace = (typeof spaceOption === "object" ? spaceOption.trailing : spaceOption) || "always";
10826
+ const cellAlignOption = options?.cellAlign;
10827
+ const defaultDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.defaultDelimiter : cellAlignOption) || "left";
10828
+ const leftAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.leftAlignmentDelimiter : cellAlignOption) || "left";
10829
+ const centerAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.centerAlignmentDelimiter : cellAlignOption) || "center";
10830
+ const rightAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.rightAlignmentDelimiter : cellAlignOption) || "right";
10831
+ return {
10832
+ leadingSpace,
10833
+ trailingSpace,
10834
+ cellAlignByDelimiter: {
10835
+ none: adjustAlign(defaultDelimiterCellAlign),
10836
+ left: adjustAlign(leftAlignmentDelimiterCellAlign),
10837
+ center: adjustAlign(centerAlignmentDelimiterCellAlign),
10838
+ right: adjustAlign(rightAlignmentDelimiterCellAlign)
10839
+ }
10840
+ };
10841
+ /**
10842
+ * Adjust the alignment option based on the spacing options.
10843
+ */
10844
+ function adjustAlign(align) {
10845
+ if (align === "left") {
10846
+ if (trailingSpace === "always") return "left";
10847
+ return "ignore";
10848
+ }
10849
+ if (align === "center") {
10850
+ if (leadingSpace === "always" && trailingSpace === "always") return "center";
10851
+ return "ignore";
10852
+ }
10853
+ if (align === "right") {
10854
+ if (leadingSpace === "always") return "right";
10855
+ return "ignore";
10856
+ }
10857
+ return align;
10858
+ }
10859
+ }
10860
+ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
10701
10861
  meta: {
10702
10862
  type: "layout",
10703
10863
  docs: {
10704
- description: "enforce consistent alignment of table pipes",
10705
- categories: ["standard"],
10706
- listCategory: "Decorative"
10707
- },
10708
- fixable: "code",
10709
- hasSuggestions: false,
10710
- schema: [{
10711
- type: "object",
10712
- properties: { column: { enum: ["minimum", "consistent"] } },
10713
- additionalProperties: false
10714
- }],
10715
- messages: {
10716
- addSpaces: "Table pipe should be aligned at column {{expected}} (add {{count}} character{{plural}}).",
10717
- removeSpaces: "Table pipe should be aligned at column {{expected}} (remove {{count}} character{{plural}})."
10718
- }
10719
- },
10720
- create(context) {
10721
- const sourceCode = context.sourceCode;
10722
- const columnOption = (context.options[0] || {}).column || "minimum";
10723
- class TableContext {
10724
- rows;
10725
- columnCount;
10726
- _cacheHasSpaceBetweenContentAndTrailingPipe = /* @__PURE__ */ new Map();
10727
- _cacheExpectedPipePosition = /* @__PURE__ */ new Map();
10728
- constructor(parsed) {
10729
- const rows = [parsedTableRowToRowData(parsed.headerRow), parsedTableDelimiterRowToRowData(parsed.delimiterRow)];
10730
- for (const bodyRow of parsed.bodyRows) rows.push(parsedTableRowToRowData(bodyRow));
10731
- this.rows = rows;
10732
- let columnCount = 0;
10733
- for (const row of rows) columnCount = Math.max(columnCount, row.cells.length);
10734
- this.columnCount = columnCount;
10735
- }
10736
- /**
10737
- * Get the expected pipe position for the index
10738
- */
10739
- getExpectedPipePosition(pipeIndex) {
10740
- let v = this._cacheExpectedPipePosition.get(pipeIndex);
10741
- if (v !== void 0) return v;
10742
- v = this._computeExpectedPipePositionWithoutCache(pipeIndex);
10743
- this._cacheExpectedPipePosition.set(pipeIndex, v);
10744
- return v;
10745
- }
10746
- /**
10747
- * Check if there is at least one space between content and trailing pipe
10748
- * for the index
10749
- *
10750
- * This is used to determine if the pipe should be aligned with a space before it.
10751
- */
10752
- hasSpaceBetweenContentAndTrailingPipe(pipeIndex) {
10753
- if (pipeIndex === 0) return false;
10754
- let v = this._cacheHasSpaceBetweenContentAndTrailingPipe.get(pipeIndex);
10755
- if (v != null) return v;
10756
- v = this._hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex);
10757
- this._cacheHasSpaceBetweenContentAndTrailingPipe.set(pipeIndex, v);
10758
- return v;
10759
- }
10760
- /**
10761
- * Get the expected pipe position for the index
10762
- */
10763
- _computeExpectedPipePositionWithoutCache(pipeIndex) {
10764
- if (pipeIndex === 0) {
10765
- const firstCell = this.rows[0].cells[0];
10766
- const firstToken = firstCell.leadingPipe ?? firstCell.content;
10767
- if (!firstToken) return null;
10768
- return getTextWidth(sourceCode.lines[firstToken.loc.start.line - 1].slice(0, firstToken.loc.start.column - 1));
10769
- }
10770
- if (columnOption === "minimum") return this.getMinimumPipePosition(pipeIndex);
10771
- else if (columnOption === "consistent") {
10772
- const columnIndex = pipeIndex - 1;
10773
- for (const row of this.rows) {
10774
- if (row.cells.length <= columnIndex) continue;
10775
- const cell = row.cells[columnIndex];
10776
- if (cell.type === "delimiter" || !cell.trailingPipe) continue;
10777
- const width = getTextWidth(sourceCode.lines[cell.trailingPipe.loc.start.line - 1].slice(0, cell.trailingPipe.loc.start.column - 1));
10778
- return Math.max(width, this.getMinimumPipePosition(pipeIndex) || 0);
10779
- }
10780
- }
10781
- return null;
10782
- }
10783
- /**
10784
- * Get the minimum pipe position for the index
10785
- */
10786
- getMinimumPipePosition(pipeIndex) {
10787
- const needSpaceBeforePipe = this.hasSpaceBetweenContentAndTrailingPipe(pipeIndex);
10788
- let maxWidth = 0;
10789
- const columnIndex = pipeIndex - 1;
10790
- for (const row of this.rows) {
10791
- if (row.cells.length <= columnIndex) continue;
10792
- const cell = row.cells[columnIndex];
10793
- let width;
10794
- if (cell.type === "delimiter") {
10795
- const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
10796
- width = getTextWidth(sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1)) + minimumDelimiterLength;
10797
- } else {
10798
- if (!cell.content) continue;
10799
- width = getTextWidth(sourceCode.lines[cell.content.loc.end.line - 1].slice(0, cell.content.loc.end.column - 1));
10800
- }
10801
- if (needSpaceBeforePipe) width += 1;
10802
- maxWidth = Math.max(maxWidth, width);
10803
- }
10804
- return maxWidth;
10805
- }
10806
- /**
10807
- * Check if there is at least one space between content and trailing pipe
10808
- */
10809
- _hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex) {
10810
- const columnIndex = pipeIndex - 1;
10811
- for (const row of this.rows) {
10812
- if (row.cells.length <= columnIndex) continue;
10813
- const cell = row.cells[columnIndex];
10814
- if (!cell.trailingPipe) continue;
10815
- let content;
10816
- if (cell.type === "delimiter") content = cell.delimiter;
10817
- else {
10818
- if (!cell.content) continue;
10819
- content = cell.content;
10820
- }
10821
- if (content.range[1] < cell.trailingPipe.range[0]) continue;
10822
- return false;
10823
- }
10824
- return true;
10825
- }
10826
- }
10827
- /**
10828
- * Verify the table pipes
10829
- */
10830
- function verifyTablePipes(table) {
10831
- const targetRows = [...table.rows];
10832
- for (const row of targetRows) for (let pipeIndex = 0; pipeIndex <= table.columnCount; pipeIndex++) if (!verifyRowPipe(row, pipeIndex, table)) break;
10833
- }
10834
- /**
10835
- * Verify the pipe in the row
10836
- */
10837
- function verifyRowPipe(row, pipeIndex, table) {
10838
- let cellIndex;
10839
- let pipe;
10840
- if (pipeIndex === 0) {
10841
- cellIndex = 0;
10842
- pipe = "leadingPipe";
10843
- } else {
10844
- cellIndex = pipeIndex - 1;
10845
- pipe = "trailingPipe";
10846
- }
10847
- if (row.cells.length <= cellIndex) return true;
10848
- const cell = row.cells[cellIndex];
10849
- const pipeToken = cell[pipe];
10850
- if (!pipeToken) return true;
10851
- return verifyPipe(pipeToken, pipeIndex, table, cell);
10852
- }
10853
- /**
10854
- * Verify the pipe position
10855
- */
10856
- function verifyPipe(pipe, pipeIndex, table, cell) {
10857
- const expected = table.getExpectedPipePosition(pipeIndex);
10858
- if (expected == null) return true;
10859
- const actual = getTextWidth(sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1));
10860
- const diff = expected - actual;
10861
- if (diff === 0) return true;
10862
- context.report({
10863
- loc: pipe.loc,
10864
- messageId: diff > 0 ? "addSpaces" : "removeSpaces",
10865
- data: {
10866
- expected: String(expected),
10867
- count: String(Math.abs(diff)),
10868
- plural: Math.abs(diff) === 1 ? "" : "s"
10869
- },
10870
- fix(fixer) {
10871
- if (diff > 0) {
10872
- if (pipeIndex === 0 || cell.type === "cell") return fixer.insertTextBeforeRange(pipe.range, " ".repeat(diff));
10873
- return fixer.insertTextAfterRange([cell.delimiter.range[0], cell.delimiter.range[0] + 1], "-".repeat(diff));
10874
- }
10875
- const baseEdit = fixRemoveSpaces();
10876
- if (baseEdit) return baseEdit;
10877
- if (pipeIndex === 0 || cell.type === "cell") return null;
10878
- const beforeDelimiter = sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1);
10879
- const widthBeforeDelimiter = getTextWidth(beforeDelimiter);
10880
- const newLength = expected - widthBeforeDelimiter;
10881
- const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
10882
- const spaceAfter = table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? " " : "";
10883
- if (newLength < minimumDelimiterLength + spaceAfter.length) return null;
10884
- const delimiterPrefix = cell.align === "left" || cell.align === "center" ? ":" : "";
10885
- const delimiterSuffix = (cell.align === "right" || cell.align === "center" ? ":" : "") + spaceAfter;
10886
- const newDelimiter = "-".repeat(newLength - delimiterPrefix.length - delimiterSuffix.length);
10887
- return fixer.replaceTextRange([cell.delimiter.range[0], pipe.range[0]], delimiterPrefix + newDelimiter + delimiterSuffix);
10888
- /**
10889
- * Fixer to remove spaces before the pipe
10890
- */
10891
- function fixRemoveSpaces() {
10892
- const beforePipe = sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1);
10893
- const trimmedBeforePipe = beforePipe.trimEnd();
10894
- const spacesBeforePipeLength = beforePipe.length - trimmedBeforePipe.length;
10895
- const widthBeforePipe = getTextWidth(trimmedBeforePipe);
10896
- const newSpacesLength = expected - widthBeforePipe;
10897
- if (newSpacesLength < (table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? 1 : 0)) return null;
10898
- return fixer.replaceTextRange([pipe.range[0] - spacesBeforePipeLength, pipe.range[0]], " ".repeat(newSpacesLength));
10899
- }
10900
- }
10901
- });
10902
- return false;
10903
- }
10904
- /**
10905
- * Get the minimum delimiter length based on alignment
10906
- */
10907
- function getMinimumDelimiterLength(align) {
10908
- return align === "none" ? 1 : align === "center" ? 3 : 2;
10909
- }
10910
- return { table(node) {
10911
- const parsed = parseTable(sourceCode, node);
10912
- if (!parsed) return;
10913
- verifyTablePipes(new TableContext(parsed));
10914
- } };
10915
- }
10916
- });
10917
- /**
10918
- * Convert a parsed table row to row data
10919
- */
10920
- function parsedTableRowToRowData(parsedRow) {
10921
- return { cells: parsedRow.cells.map((cell, index) => {
10922
- const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
10923
- return {
10924
- type: "cell",
10925
- leadingPipe: cell.leadingPipe,
10926
- content: cell.cell,
10927
- trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
10928
- };
10929
- }) };
10930
- }
10931
- /**
10932
- * Convert a parsed table delimiter row to row data
10933
- */
10934
- function parsedTableDelimiterRowToRowData(parsedDelimiterRow) {
10935
- return { cells: parsedDelimiterRow.delimiters.map((cell, index) => {
10936
- const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
10937
- return {
10938
- type: "delimiter",
10939
- leadingPipe: cell.leadingPipe,
10940
- delimiter: cell.delimiter,
10941
- align: cell.delimiter.align,
10942
- trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
10943
- };
10944
- }) };
10945
- }
10946
-
10947
- //#endregion
10948
- //#region src/rules/table-pipe-spacing.ts
10949
- /**
10950
- * Parsed options
10951
- */
10952
- function parseOptions(options) {
10953
- const spaceOption = options?.space;
10954
- const leadingSpace = (typeof spaceOption === "object" ? spaceOption.leading : spaceOption) || "always";
10955
- const trailingSpace = (typeof spaceOption === "object" ? spaceOption.trailing : spaceOption) || "always";
10956
- const cellAlignOption = options?.cellAlign;
10957
- const defaultDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.defaultDelimiter : cellAlignOption) || "left";
10958
- const leftAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.leftAlignmentDelimiter : cellAlignOption) || "left";
10959
- const centerAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.centerAlignmentDelimiter : cellAlignOption) || "center";
10960
- const rightAlignmentDelimiterCellAlign = (typeof cellAlignOption === "object" ? cellAlignOption.rightAlignmentDelimiter : cellAlignOption) || "right";
10961
- return {
10962
- leadingSpace,
10963
- trailingSpace,
10964
- cellAlignByDelimiter: {
10965
- none: adjustAlign(defaultDelimiterCellAlign),
10966
- left: adjustAlign(leftAlignmentDelimiterCellAlign),
10967
- center: adjustAlign(centerAlignmentDelimiterCellAlign),
10968
- right: adjustAlign(rightAlignmentDelimiterCellAlign)
10969
- }
10970
- };
10971
- /**
10972
- * Adjust the alignment option based on the spacing options.
10973
- */
10974
- function adjustAlign(align) {
10975
- if (align === "left") {
10976
- if (trailingSpace === "always") return "left";
10977
- return "ignore";
10978
- }
10979
- if (align === "center") {
10980
- if (leadingSpace === "always" && trailingSpace === "always") return "center";
10981
- return "ignore";
10982
- }
10983
- if (align === "right") {
10984
- if (leadingSpace === "always") return "right";
10985
- return "ignore";
10986
- }
10987
- return align;
10988
- }
10989
- }
10990
- var table_pipe_spacing_default = createRule("table-pipe-spacing", {
10991
- meta: {
10992
- type: "layout",
10993
- docs: {
10994
- description: "enforce consistent spacing around table pipes",
10864
+ description: "enforce consistent spacing around table pipes",
10995
10865
  categories: ["standard"],
10996
10866
  listCategory: "Whitespace"
10997
10867
  },
@@ -11060,6 +10930,7 @@ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
11060
10930
  create(context) {
11061
10931
  const sourceCode = context.sourceCode;
11062
10932
  const options = parseOptions(context.options[0]);
10933
+ currentOption.set(sourceCode, options);
11063
10934
  /**
11064
10935
  * Verify for the leading pipe.
11065
10936
  */
@@ -11317,6 +11188,256 @@ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
11317
11188
  }
11318
11189
  });
11319
11190
 
11191
+ //#endregion
11192
+ //#region src/rules/table-pipe-alignment.ts
11193
+ var table_pipe_alignment_default = createRule("table-pipe-alignment", {
11194
+ meta: {
11195
+ type: "layout",
11196
+ docs: {
11197
+ description: "enforce consistent alignment of table pipes",
11198
+ categories: ["standard"],
11199
+ listCategory: "Decorative"
11200
+ },
11201
+ fixable: "code",
11202
+ hasSuggestions: false,
11203
+ schema: [{
11204
+ type: "object",
11205
+ properties: { column: { enum: ["minimum", "consistent"] } },
11206
+ additionalProperties: false
11207
+ }],
11208
+ messages: {
11209
+ addSpaces: "Table pipe should be aligned at column {{expected}} (add {{count}} character{{plural}}).",
11210
+ removeSpaces: "Table pipe should be aligned at column {{expected}} (remove {{count}} character{{plural}})."
11211
+ }
11212
+ },
11213
+ create(context) {
11214
+ const sourceCode = context.sourceCode;
11215
+ const columnOption = (context.options[0] || {}).column || "minimum";
11216
+ class TableContext {
11217
+ rows;
11218
+ columnCount;
11219
+ _cacheHasSpaceBetweenContentAndTrailingPipe = /* @__PURE__ */ new Map();
11220
+ _cacheExpectedPipePosition = /* @__PURE__ */ new Map();
11221
+ constructor(parsed) {
11222
+ const rows = [parsedTableRowToRowData(parsed.headerRow), parsedTableDelimiterRowToRowData(parsed.delimiterRow)];
11223
+ for (const bodyRow of parsed.bodyRows) rows.push(parsedTableRowToRowData(bodyRow));
11224
+ this.rows = rows;
11225
+ let columnCount = 0;
11226
+ for (const row of rows) columnCount = Math.max(columnCount, row.cells.length);
11227
+ this.columnCount = columnCount;
11228
+ }
11229
+ /**
11230
+ * Get the expected pipe position for the index
11231
+ */
11232
+ getExpectedPipePosition(pipeIndex) {
11233
+ let v = this._cacheExpectedPipePosition.get(pipeIndex);
11234
+ if (v !== void 0) return v;
11235
+ v = this._computeExpectedPipePositionWithoutCache(pipeIndex);
11236
+ this._cacheExpectedPipePosition.set(pipeIndex, v);
11237
+ return v;
11238
+ }
11239
+ /**
11240
+ * Check if there is at least one space between content and trailing pipe
11241
+ * for the index
11242
+ *
11243
+ * This is used to determine if the pipe should be aligned with a space before it.
11244
+ */
11245
+ hasSpaceBetweenContentAndTrailingPipe(pipeIndex) {
11246
+ if (pipeIndex === 0) return false;
11247
+ let v = this._cacheHasSpaceBetweenContentAndTrailingPipe.get(pipeIndex);
11248
+ if (v != null) return v;
11249
+ v = this._hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex);
11250
+ this._cacheHasSpaceBetweenContentAndTrailingPipe.set(pipeIndex, v);
11251
+ return v;
11252
+ }
11253
+ /**
11254
+ * Get the expected pipe position for the index
11255
+ */
11256
+ _computeExpectedPipePositionWithoutCache(pipeIndex) {
11257
+ if (pipeIndex === 0) {
11258
+ const firstCell = this.rows[0].cells[0];
11259
+ const firstToken = firstCell.leadingPipe ?? firstCell.content;
11260
+ if (!firstToken) return null;
11261
+ return getTextWidth(sourceCode.lines[firstToken.loc.start.line - 1].slice(0, firstToken.loc.start.column - 1));
11262
+ }
11263
+ if (columnOption === "minimum") return this.getMinimumPipePosition(pipeIndex);
11264
+ else if (columnOption === "consistent") {
11265
+ const columnIndex = pipeIndex - 1;
11266
+ for (const row of this.rows) {
11267
+ if (row.cells.length <= columnIndex) continue;
11268
+ const cell = row.cells[columnIndex];
11269
+ if (cell.type === "delimiter" || !cell.trailingPipe) continue;
11270
+ const width = getTextWidth(sourceCode.lines[cell.trailingPipe.loc.start.line - 1].slice(0, cell.trailingPipe.loc.start.column - 1));
11271
+ return Math.max(width, this.getMinimumPipePosition(pipeIndex) || 0);
11272
+ }
11273
+ }
11274
+ return null;
11275
+ }
11276
+ /**
11277
+ * Get the minimum pipe position for the index
11278
+ */
11279
+ getMinimumPipePosition(pipeIndex) {
11280
+ const spacingRuleOptions = getCurrentTablePipeSpacingOption(sourceCode);
11281
+ const needSpaceBeforePipe = spacingRuleOptions ? spacingRuleOptions.trailingSpace === "always" : this.hasSpaceBetweenContentAndTrailingPipe(pipeIndex);
11282
+ let maxWidth = 0;
11283
+ const columnIndex = pipeIndex - 1;
11284
+ for (const row of this.rows) {
11285
+ if (row.cells.length <= columnIndex) continue;
11286
+ const cell = row.cells[columnIndex];
11287
+ let width;
11288
+ if (cell.type === "delimiter") {
11289
+ const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
11290
+ width = getTextWidth(sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1)) + minimumDelimiterLength;
11291
+ } else {
11292
+ if (!cell.content) continue;
11293
+ width = getTextWidth(sourceCode.lines[cell.content.loc.end.line - 1].slice(0, cell.content.loc.end.column - 1));
11294
+ }
11295
+ if (needSpaceBeforePipe) width += 1;
11296
+ maxWidth = Math.max(maxWidth, width);
11297
+ }
11298
+ return maxWidth;
11299
+ }
11300
+ /**
11301
+ * Check if there is at least one space between content and trailing pipe
11302
+ */
11303
+ _hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex) {
11304
+ const columnIndex = pipeIndex - 1;
11305
+ for (const row of this.rows) {
11306
+ if (row.cells.length <= columnIndex) continue;
11307
+ const cell = row.cells[columnIndex];
11308
+ if (!cell.trailingPipe) continue;
11309
+ let content;
11310
+ if (cell.type === "delimiter") content = cell.delimiter;
11311
+ else {
11312
+ if (!cell.content) continue;
11313
+ content = cell.content;
11314
+ }
11315
+ if (content.range[1] < cell.trailingPipe.range[0]) continue;
11316
+ return false;
11317
+ }
11318
+ return true;
11319
+ }
11320
+ }
11321
+ /**
11322
+ * Verify the table pipes
11323
+ */
11324
+ function verifyTablePipes(table) {
11325
+ const targetRows = [...table.rows];
11326
+ for (const row of targetRows) for (let pipeIndex = 0; pipeIndex <= table.columnCount; pipeIndex++) if (!verifyRowPipe(row, pipeIndex, table)) break;
11327
+ }
11328
+ /**
11329
+ * Verify the pipe in the row
11330
+ */
11331
+ function verifyRowPipe(row, pipeIndex, table) {
11332
+ let cellIndex;
11333
+ let pipe;
11334
+ if (pipeIndex === 0) {
11335
+ cellIndex = 0;
11336
+ pipe = "leadingPipe";
11337
+ } else {
11338
+ cellIndex = pipeIndex - 1;
11339
+ pipe = "trailingPipe";
11340
+ }
11341
+ if (row.cells.length <= cellIndex) return true;
11342
+ const cell = row.cells[cellIndex];
11343
+ const pipeToken = cell[pipe];
11344
+ if (!pipeToken) return true;
11345
+ return verifyPipe(pipeToken, pipeIndex, table, cell);
11346
+ }
11347
+ /**
11348
+ * Verify the pipe position
11349
+ */
11350
+ function verifyPipe(pipe, pipeIndex, table, cell) {
11351
+ const expected = table.getExpectedPipePosition(pipeIndex);
11352
+ if (expected == null) return true;
11353
+ const actual = getTextWidth(sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1));
11354
+ const diff = expected - actual;
11355
+ if (diff === 0) return true;
11356
+ context.report({
11357
+ loc: pipe.loc,
11358
+ messageId: diff > 0 ? "addSpaces" : "removeSpaces",
11359
+ data: {
11360
+ expected: String(expected),
11361
+ count: String(Math.abs(diff)),
11362
+ plural: Math.abs(diff) === 1 ? "" : "s"
11363
+ },
11364
+ fix(fixer) {
11365
+ if (diff > 0) {
11366
+ if (pipeIndex === 0 || cell.type === "cell") return fixer.insertTextBeforeRange(pipe.range, " ".repeat(diff));
11367
+ return fixer.insertTextAfterRange([cell.delimiter.range[0], cell.delimiter.range[0] + 1], "-".repeat(diff));
11368
+ }
11369
+ const baseEdit = fixRemoveSpaces();
11370
+ if (baseEdit) return baseEdit;
11371
+ if (pipeIndex === 0 || cell.type === "cell") return null;
11372
+ const beforeDelimiter = sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1);
11373
+ const widthBeforeDelimiter = getTextWidth(beforeDelimiter);
11374
+ const newLength = expected - widthBeforeDelimiter;
11375
+ const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
11376
+ const spaceAfter = table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? " " : "";
11377
+ if (newLength < minimumDelimiterLength + spaceAfter.length) return null;
11378
+ const delimiterPrefix = cell.align === "left" || cell.align === "center" ? ":" : "";
11379
+ const delimiterSuffix = (cell.align === "right" || cell.align === "center" ? ":" : "") + spaceAfter;
11380
+ const newDelimiter = "-".repeat(newLength - delimiterPrefix.length - delimiterSuffix.length);
11381
+ return fixer.replaceTextRange([cell.delimiter.range[0], pipe.range[0]], delimiterPrefix + newDelimiter + delimiterSuffix);
11382
+ /**
11383
+ * Fixer to remove spaces before the pipe
11384
+ */
11385
+ function fixRemoveSpaces() {
11386
+ const beforePipe = sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1);
11387
+ const trimmedBeforePipe = beforePipe.trimEnd();
11388
+ const spacesBeforePipeLength = beforePipe.length - trimmedBeforePipe.length;
11389
+ const widthBeforePipe = getTextWidth(trimmedBeforePipe);
11390
+ const newSpacesLength = expected - widthBeforePipe;
11391
+ if (newSpacesLength < (table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? 1 : 0)) return null;
11392
+ return fixer.replaceTextRange([pipe.range[0] - spacesBeforePipeLength, pipe.range[0]], " ".repeat(newSpacesLength));
11393
+ }
11394
+ }
11395
+ });
11396
+ return false;
11397
+ }
11398
+ /**
11399
+ * Get the minimum delimiter length based on alignment
11400
+ */
11401
+ function getMinimumDelimiterLength(align) {
11402
+ return align === "none" ? 1 : align === "center" ? 3 : 2;
11403
+ }
11404
+ return { table(node) {
11405
+ const parsed = parseTable(sourceCode, node);
11406
+ if (!parsed) return;
11407
+ verifyTablePipes(new TableContext(parsed));
11408
+ } };
11409
+ }
11410
+ });
11411
+ /**
11412
+ * Convert a parsed table row to row data
11413
+ */
11414
+ function parsedTableRowToRowData(parsedRow) {
11415
+ return { cells: parsedRow.cells.map((cell, index) => {
11416
+ const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
11417
+ return {
11418
+ type: "cell",
11419
+ leadingPipe: cell.leadingPipe,
11420
+ content: cell.cell,
11421
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
11422
+ };
11423
+ }) };
11424
+ }
11425
+ /**
11426
+ * Convert a parsed table delimiter row to row data
11427
+ */
11428
+ function parsedTableDelimiterRowToRowData(parsedDelimiterRow) {
11429
+ return { cells: parsedDelimiterRow.delimiters.map((cell, index) => {
11430
+ const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
11431
+ return {
11432
+ type: "delimiter",
11433
+ leadingPipe: cell.leadingPipe,
11434
+ delimiter: cell.delimiter,
11435
+ align: cell.delimiter.align,
11436
+ trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
11437
+ };
11438
+ }) };
11439
+ }
11440
+
11320
11441
  //#endregion
11321
11442
  //#region src/rules/thematic-break-character-style.ts
11322
11443
  var thematic_break_character_style_default = createRule("thematic-break-character-style", {
@@ -11533,7 +11654,9 @@ const rules$1 = [
11533
11654
  bullet_list_marker_style_default,
11534
11655
  canonical_code_block_language_default,
11535
11656
  code_fence_length_default,
11657
+ code_fence_spacing_default,
11536
11658
  code_fence_style_default,
11659
+ custom_container_marker_spacing_default,
11537
11660
  definitions_last_default,
11538
11661
  emoji_notation_default,
11539
11662
  emphasis_delimiters_style_default,
@@ -11678,7 +11801,7 @@ var meta_exports = /* @__PURE__ */ __export({
11678
11801
  version: () => version
11679
11802
  });
11680
11803
  const name = "eslint-plugin-markdown-preferences";
11681
- const version = "0.29.0";
11804
+ const version = "0.30.0";
11682
11805
 
11683
11806
  //#endregion
11684
11807
  //#region src/language/extensions/micromark-custom-container.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-markdown-preferences",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "ESLint plugin that enforces our markdown preferences",
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,7 +25,7 @@
25
25
  "markdownlint": "npx -y markdownlint-cli2 .",
26
26
  "test": "npm run mocha -- \"tests/src/**/*.ts\" --reporter=dot --timeout=60000",
27
27
  "test:debug": "node --experimental-strip-types --experimental-transform-types ./node_modules/mocha/bin/mocha.js \"tests/src/**/*.ts\" --reporter=dot --timeout=60000",
28
- "cover": "c8 --reporter=lcov npm run test",
28
+ "cover": "c8 --reporter=lcov --reporter=text npm run test:debug",
29
29
  "test:update": "npm run mocha -- \"tests/src/**/*.ts\" --reporter=dot --update",
30
30
  "update": "npm run ts -- ./tools/update.ts && npm run eslint-fix",
31
31
  "update:resources": "npm run ts -- ./tools/update-resources.ts && npm run eslint-fix",