eslint-plugin-markdown-preferences 0.29.0 β 0.29.1
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 +9 -11
- package/lib/index.d.ts +1 -1
- package/lib/index.js +258 -248
- package/package.json +2 -2
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
|
-
|
|
18
|
-
- π§© **
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
- π§ **Auto-fix support
|
|
23
|
-
Most rules support ESLint's `--fix` option for
|
|
24
|
-
-
|
|
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-->
|
package/lib/index.d.ts
CHANGED
|
@@ -516,7 +516,7 @@ declare namespace meta_d_exports {
|
|
|
516
516
|
export { name, version };
|
|
517
517
|
}
|
|
518
518
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
519
|
-
declare const version: "0.29.
|
|
519
|
+
declare const version: "0.29.1";
|
|
520
520
|
//#endregion
|
|
521
521
|
//#region src/language/ast-types.d.ts
|
|
522
522
|
type Node = mdast.Node;
|
package/lib/index.js
CHANGED
|
@@ -10696,257 +10696,16 @@ var table_leading_trailing_pipes_default = createRule("table-leading-trailing-pi
|
|
|
10696
10696
|
});
|
|
10697
10697
|
|
|
10698
10698
|
//#endregion
|
|
10699
|
-
//#region src/rules/table-pipe-
|
|
10700
|
-
|
|
10701
|
-
meta: {
|
|
10702
|
-
type: "layout",
|
|
10703
|
-
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
|
-
});
|
|
10699
|
+
//#region src/rules/table-pipe-spacing.ts
|
|
10700
|
+
const currentOption = /* @__PURE__ */ new WeakMap();
|
|
10917
10701
|
/**
|
|
10918
|
-
*
|
|
10702
|
+
* Get the current options for the given source code.
|
|
10703
|
+
* This is a method that allows you to access the configuration of this rule from another rule.
|
|
10919
10704
|
*/
|
|
10920
|
-
function
|
|
10921
|
-
return
|
|
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
|
-
}) };
|
|
10705
|
+
function getCurrentTablePipeSpacingOption(sourceCode) {
|
|
10706
|
+
return currentOption.get(sourceCode) ?? null;
|
|
10930
10707
|
}
|
|
10931
10708
|
/**
|
|
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
10709
|
* Parsed options
|
|
10951
10710
|
*/
|
|
10952
10711
|
function parseOptions(options) {
|
|
@@ -11060,6 +10819,7 @@ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
|
|
|
11060
10819
|
create(context) {
|
|
11061
10820
|
const sourceCode = context.sourceCode;
|
|
11062
10821
|
const options = parseOptions(context.options[0]);
|
|
10822
|
+
currentOption.set(sourceCode, options);
|
|
11063
10823
|
/**
|
|
11064
10824
|
* Verify for the leading pipe.
|
|
11065
10825
|
*/
|
|
@@ -11317,6 +11077,256 @@ var table_pipe_spacing_default = createRule("table-pipe-spacing", {
|
|
|
11317
11077
|
}
|
|
11318
11078
|
});
|
|
11319
11079
|
|
|
11080
|
+
//#endregion
|
|
11081
|
+
//#region src/rules/table-pipe-alignment.ts
|
|
11082
|
+
var table_pipe_alignment_default = createRule("table-pipe-alignment", {
|
|
11083
|
+
meta: {
|
|
11084
|
+
type: "layout",
|
|
11085
|
+
docs: {
|
|
11086
|
+
description: "enforce consistent alignment of table pipes",
|
|
11087
|
+
categories: ["standard"],
|
|
11088
|
+
listCategory: "Decorative"
|
|
11089
|
+
},
|
|
11090
|
+
fixable: "code",
|
|
11091
|
+
hasSuggestions: false,
|
|
11092
|
+
schema: [{
|
|
11093
|
+
type: "object",
|
|
11094
|
+
properties: { column: { enum: ["minimum", "consistent"] } },
|
|
11095
|
+
additionalProperties: false
|
|
11096
|
+
}],
|
|
11097
|
+
messages: {
|
|
11098
|
+
addSpaces: "Table pipe should be aligned at column {{expected}} (add {{count}} character{{plural}}).",
|
|
11099
|
+
removeSpaces: "Table pipe should be aligned at column {{expected}} (remove {{count}} character{{plural}})."
|
|
11100
|
+
}
|
|
11101
|
+
},
|
|
11102
|
+
create(context) {
|
|
11103
|
+
const sourceCode = context.sourceCode;
|
|
11104
|
+
const columnOption = (context.options[0] || {}).column || "minimum";
|
|
11105
|
+
class TableContext {
|
|
11106
|
+
rows;
|
|
11107
|
+
columnCount;
|
|
11108
|
+
_cacheHasSpaceBetweenContentAndTrailingPipe = /* @__PURE__ */ new Map();
|
|
11109
|
+
_cacheExpectedPipePosition = /* @__PURE__ */ new Map();
|
|
11110
|
+
constructor(parsed) {
|
|
11111
|
+
const rows = [parsedTableRowToRowData(parsed.headerRow), parsedTableDelimiterRowToRowData(parsed.delimiterRow)];
|
|
11112
|
+
for (const bodyRow of parsed.bodyRows) rows.push(parsedTableRowToRowData(bodyRow));
|
|
11113
|
+
this.rows = rows;
|
|
11114
|
+
let columnCount = 0;
|
|
11115
|
+
for (const row of rows) columnCount = Math.max(columnCount, row.cells.length);
|
|
11116
|
+
this.columnCount = columnCount;
|
|
11117
|
+
}
|
|
11118
|
+
/**
|
|
11119
|
+
* Get the expected pipe position for the index
|
|
11120
|
+
*/
|
|
11121
|
+
getExpectedPipePosition(pipeIndex) {
|
|
11122
|
+
let v = this._cacheExpectedPipePosition.get(pipeIndex);
|
|
11123
|
+
if (v !== void 0) return v;
|
|
11124
|
+
v = this._computeExpectedPipePositionWithoutCache(pipeIndex);
|
|
11125
|
+
this._cacheExpectedPipePosition.set(pipeIndex, v);
|
|
11126
|
+
return v;
|
|
11127
|
+
}
|
|
11128
|
+
/**
|
|
11129
|
+
* Check if there is at least one space between content and trailing pipe
|
|
11130
|
+
* for the index
|
|
11131
|
+
*
|
|
11132
|
+
* This is used to determine if the pipe should be aligned with a space before it.
|
|
11133
|
+
*/
|
|
11134
|
+
hasSpaceBetweenContentAndTrailingPipe(pipeIndex) {
|
|
11135
|
+
if (pipeIndex === 0) return false;
|
|
11136
|
+
let v = this._cacheHasSpaceBetweenContentAndTrailingPipe.get(pipeIndex);
|
|
11137
|
+
if (v != null) return v;
|
|
11138
|
+
v = this._hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex);
|
|
11139
|
+
this._cacheHasSpaceBetweenContentAndTrailingPipe.set(pipeIndex, v);
|
|
11140
|
+
return v;
|
|
11141
|
+
}
|
|
11142
|
+
/**
|
|
11143
|
+
* Get the expected pipe position for the index
|
|
11144
|
+
*/
|
|
11145
|
+
_computeExpectedPipePositionWithoutCache(pipeIndex) {
|
|
11146
|
+
if (pipeIndex === 0) {
|
|
11147
|
+
const firstCell = this.rows[0].cells[0];
|
|
11148
|
+
const firstToken = firstCell.leadingPipe ?? firstCell.content;
|
|
11149
|
+
if (!firstToken) return null;
|
|
11150
|
+
return getTextWidth(sourceCode.lines[firstToken.loc.start.line - 1].slice(0, firstToken.loc.start.column - 1));
|
|
11151
|
+
}
|
|
11152
|
+
if (columnOption === "minimum") return this.getMinimumPipePosition(pipeIndex);
|
|
11153
|
+
else if (columnOption === "consistent") {
|
|
11154
|
+
const columnIndex = pipeIndex - 1;
|
|
11155
|
+
for (const row of this.rows) {
|
|
11156
|
+
if (row.cells.length <= columnIndex) continue;
|
|
11157
|
+
const cell = row.cells[columnIndex];
|
|
11158
|
+
if (cell.type === "delimiter" || !cell.trailingPipe) continue;
|
|
11159
|
+
const width = getTextWidth(sourceCode.lines[cell.trailingPipe.loc.start.line - 1].slice(0, cell.trailingPipe.loc.start.column - 1));
|
|
11160
|
+
return Math.max(width, this.getMinimumPipePosition(pipeIndex) || 0);
|
|
11161
|
+
}
|
|
11162
|
+
}
|
|
11163
|
+
return null;
|
|
11164
|
+
}
|
|
11165
|
+
/**
|
|
11166
|
+
* Get the minimum pipe position for the index
|
|
11167
|
+
*/
|
|
11168
|
+
getMinimumPipePosition(pipeIndex) {
|
|
11169
|
+
const spacingRuleOptions = getCurrentTablePipeSpacingOption(sourceCode);
|
|
11170
|
+
const needSpaceBeforePipe = spacingRuleOptions ? spacingRuleOptions.trailingSpace === "always" : this.hasSpaceBetweenContentAndTrailingPipe(pipeIndex);
|
|
11171
|
+
let maxWidth = 0;
|
|
11172
|
+
const columnIndex = pipeIndex - 1;
|
|
11173
|
+
for (const row of this.rows) {
|
|
11174
|
+
if (row.cells.length <= columnIndex) continue;
|
|
11175
|
+
const cell = row.cells[columnIndex];
|
|
11176
|
+
let width;
|
|
11177
|
+
if (cell.type === "delimiter") {
|
|
11178
|
+
const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
|
|
11179
|
+
width = getTextWidth(sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1)) + minimumDelimiterLength;
|
|
11180
|
+
} else {
|
|
11181
|
+
if (!cell.content) continue;
|
|
11182
|
+
width = getTextWidth(sourceCode.lines[cell.content.loc.end.line - 1].slice(0, cell.content.loc.end.column - 1));
|
|
11183
|
+
}
|
|
11184
|
+
if (needSpaceBeforePipe) width += 1;
|
|
11185
|
+
maxWidth = Math.max(maxWidth, width);
|
|
11186
|
+
}
|
|
11187
|
+
return maxWidth;
|
|
11188
|
+
}
|
|
11189
|
+
/**
|
|
11190
|
+
* Check if there is at least one space between content and trailing pipe
|
|
11191
|
+
*/
|
|
11192
|
+
_hasSpaceBetweenContentAndTrailingPipeWithoutCache(pipeIndex) {
|
|
11193
|
+
const columnIndex = pipeIndex - 1;
|
|
11194
|
+
for (const row of this.rows) {
|
|
11195
|
+
if (row.cells.length <= columnIndex) continue;
|
|
11196
|
+
const cell = row.cells[columnIndex];
|
|
11197
|
+
if (!cell.trailingPipe) continue;
|
|
11198
|
+
let content;
|
|
11199
|
+
if (cell.type === "delimiter") content = cell.delimiter;
|
|
11200
|
+
else {
|
|
11201
|
+
if (!cell.content) continue;
|
|
11202
|
+
content = cell.content;
|
|
11203
|
+
}
|
|
11204
|
+
if (content.range[1] < cell.trailingPipe.range[0]) continue;
|
|
11205
|
+
return false;
|
|
11206
|
+
}
|
|
11207
|
+
return true;
|
|
11208
|
+
}
|
|
11209
|
+
}
|
|
11210
|
+
/**
|
|
11211
|
+
* Verify the table pipes
|
|
11212
|
+
*/
|
|
11213
|
+
function verifyTablePipes(table) {
|
|
11214
|
+
const targetRows = [...table.rows];
|
|
11215
|
+
for (const row of targetRows) for (let pipeIndex = 0; pipeIndex <= table.columnCount; pipeIndex++) if (!verifyRowPipe(row, pipeIndex, table)) break;
|
|
11216
|
+
}
|
|
11217
|
+
/**
|
|
11218
|
+
* Verify the pipe in the row
|
|
11219
|
+
*/
|
|
11220
|
+
function verifyRowPipe(row, pipeIndex, table) {
|
|
11221
|
+
let cellIndex;
|
|
11222
|
+
let pipe;
|
|
11223
|
+
if (pipeIndex === 0) {
|
|
11224
|
+
cellIndex = 0;
|
|
11225
|
+
pipe = "leadingPipe";
|
|
11226
|
+
} else {
|
|
11227
|
+
cellIndex = pipeIndex - 1;
|
|
11228
|
+
pipe = "trailingPipe";
|
|
11229
|
+
}
|
|
11230
|
+
if (row.cells.length <= cellIndex) return true;
|
|
11231
|
+
const cell = row.cells[cellIndex];
|
|
11232
|
+
const pipeToken = cell[pipe];
|
|
11233
|
+
if (!pipeToken) return true;
|
|
11234
|
+
return verifyPipe(pipeToken, pipeIndex, table, cell);
|
|
11235
|
+
}
|
|
11236
|
+
/**
|
|
11237
|
+
* Verify the pipe position
|
|
11238
|
+
*/
|
|
11239
|
+
function verifyPipe(pipe, pipeIndex, table, cell) {
|
|
11240
|
+
const expected = table.getExpectedPipePosition(pipeIndex);
|
|
11241
|
+
if (expected == null) return true;
|
|
11242
|
+
const actual = getTextWidth(sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1));
|
|
11243
|
+
const diff = expected - actual;
|
|
11244
|
+
if (diff === 0) return true;
|
|
11245
|
+
context.report({
|
|
11246
|
+
loc: pipe.loc,
|
|
11247
|
+
messageId: diff > 0 ? "addSpaces" : "removeSpaces",
|
|
11248
|
+
data: {
|
|
11249
|
+
expected: String(expected),
|
|
11250
|
+
count: String(Math.abs(diff)),
|
|
11251
|
+
plural: Math.abs(diff) === 1 ? "" : "s"
|
|
11252
|
+
},
|
|
11253
|
+
fix(fixer) {
|
|
11254
|
+
if (diff > 0) {
|
|
11255
|
+
if (pipeIndex === 0 || cell.type === "cell") return fixer.insertTextBeforeRange(pipe.range, " ".repeat(diff));
|
|
11256
|
+
return fixer.insertTextAfterRange([cell.delimiter.range[0], cell.delimiter.range[0] + 1], "-".repeat(diff));
|
|
11257
|
+
}
|
|
11258
|
+
const baseEdit = fixRemoveSpaces();
|
|
11259
|
+
if (baseEdit) return baseEdit;
|
|
11260
|
+
if (pipeIndex === 0 || cell.type === "cell") return null;
|
|
11261
|
+
const beforeDelimiter = sourceCode.lines[cell.delimiter.loc.start.line - 1].slice(0, cell.delimiter.loc.start.column - 1);
|
|
11262
|
+
const widthBeforeDelimiter = getTextWidth(beforeDelimiter);
|
|
11263
|
+
const newLength = expected - widthBeforeDelimiter;
|
|
11264
|
+
const minimumDelimiterLength = getMinimumDelimiterLength(cell.align);
|
|
11265
|
+
const spaceAfter = table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? " " : "";
|
|
11266
|
+
if (newLength < minimumDelimiterLength + spaceAfter.length) return null;
|
|
11267
|
+
const delimiterPrefix = cell.align === "left" || cell.align === "center" ? ":" : "";
|
|
11268
|
+
const delimiterSuffix = (cell.align === "right" || cell.align === "center" ? ":" : "") + spaceAfter;
|
|
11269
|
+
const newDelimiter = "-".repeat(newLength - delimiterPrefix.length - delimiterSuffix.length);
|
|
11270
|
+
return fixer.replaceTextRange([cell.delimiter.range[0], pipe.range[0]], delimiterPrefix + newDelimiter + delimiterSuffix);
|
|
11271
|
+
/**
|
|
11272
|
+
* Fixer to remove spaces before the pipe
|
|
11273
|
+
*/
|
|
11274
|
+
function fixRemoveSpaces() {
|
|
11275
|
+
const beforePipe = sourceCode.lines[pipe.loc.start.line - 1].slice(0, pipe.loc.start.column - 1);
|
|
11276
|
+
const trimmedBeforePipe = beforePipe.trimEnd();
|
|
11277
|
+
const spacesBeforePipeLength = beforePipe.length - trimmedBeforePipe.length;
|
|
11278
|
+
const widthBeforePipe = getTextWidth(trimmedBeforePipe);
|
|
11279
|
+
const newSpacesLength = expected - widthBeforePipe;
|
|
11280
|
+
if (newSpacesLength < (table.hasSpaceBetweenContentAndTrailingPipe(pipeIndex) ? 1 : 0)) return null;
|
|
11281
|
+
return fixer.replaceTextRange([pipe.range[0] - spacesBeforePipeLength, pipe.range[0]], " ".repeat(newSpacesLength));
|
|
11282
|
+
}
|
|
11283
|
+
}
|
|
11284
|
+
});
|
|
11285
|
+
return false;
|
|
11286
|
+
}
|
|
11287
|
+
/**
|
|
11288
|
+
* Get the minimum delimiter length based on alignment
|
|
11289
|
+
*/
|
|
11290
|
+
function getMinimumDelimiterLength(align) {
|
|
11291
|
+
return align === "none" ? 1 : align === "center" ? 3 : 2;
|
|
11292
|
+
}
|
|
11293
|
+
return { table(node) {
|
|
11294
|
+
const parsed = parseTable(sourceCode, node);
|
|
11295
|
+
if (!parsed) return;
|
|
11296
|
+
verifyTablePipes(new TableContext(parsed));
|
|
11297
|
+
} };
|
|
11298
|
+
}
|
|
11299
|
+
});
|
|
11300
|
+
/**
|
|
11301
|
+
* Convert a parsed table row to row data
|
|
11302
|
+
*/
|
|
11303
|
+
function parsedTableRowToRowData(parsedRow) {
|
|
11304
|
+
return { cells: parsedRow.cells.map((cell, index) => {
|
|
11305
|
+
const nextCell = index + 1 < parsedRow.cells.length ? parsedRow.cells[index + 1] : null;
|
|
11306
|
+
return {
|
|
11307
|
+
type: "cell",
|
|
11308
|
+
leadingPipe: cell.leadingPipe,
|
|
11309
|
+
content: cell.cell,
|
|
11310
|
+
trailingPipe: nextCell ? nextCell.leadingPipe : parsedRow.trailingPipe
|
|
11311
|
+
};
|
|
11312
|
+
}) };
|
|
11313
|
+
}
|
|
11314
|
+
/**
|
|
11315
|
+
* Convert a parsed table delimiter row to row data
|
|
11316
|
+
*/
|
|
11317
|
+
function parsedTableDelimiterRowToRowData(parsedDelimiterRow) {
|
|
11318
|
+
return { cells: parsedDelimiterRow.delimiters.map((cell, index) => {
|
|
11319
|
+
const nextCell = index + 1 < parsedDelimiterRow.delimiters.length ? parsedDelimiterRow.delimiters[index + 1] : null;
|
|
11320
|
+
return {
|
|
11321
|
+
type: "delimiter",
|
|
11322
|
+
leadingPipe: cell.leadingPipe,
|
|
11323
|
+
delimiter: cell.delimiter,
|
|
11324
|
+
align: cell.delimiter.align,
|
|
11325
|
+
trailingPipe: nextCell ? nextCell.leadingPipe : parsedDelimiterRow.trailingPipe
|
|
11326
|
+
};
|
|
11327
|
+
}) };
|
|
11328
|
+
}
|
|
11329
|
+
|
|
11320
11330
|
//#endregion
|
|
11321
11331
|
//#region src/rules/thematic-break-character-style.ts
|
|
11322
11332
|
var thematic_break_character_style_default = createRule("thematic-break-character-style", {
|
|
@@ -11678,7 +11688,7 @@ var meta_exports = /* @__PURE__ */ __export({
|
|
|
11678
11688
|
version: () => version
|
|
11679
11689
|
});
|
|
11680
11690
|
const name = "eslint-plugin-markdown-preferences";
|
|
11681
|
-
const version = "0.29.
|
|
11691
|
+
const version = "0.29.1";
|
|
11682
11692
|
|
|
11683
11693
|
//#endregion
|
|
11684
11694
|
//#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.
|
|
3
|
+
"version": "0.29.1",
|
|
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",
|