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 +26 -26
- package/lib/index.d.ts +17 -1
- package/lib/index.js +483 -360
- 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-->
|
|
@@ -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
|
|
196
|
-
|
|
|
197
|
-
| [markdown-preferences/blockquote-marker-alignment](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/blockquote-marker-alignment.html)
|
|
198
|
-
| [markdown-preferences/
|
|
199
|
-
| [markdown-preferences/
|
|
200
|
-
| [markdown-preferences/
|
|
201
|
-
| [markdown-preferences/link-
|
|
202
|
-
| [markdown-preferences/link-
|
|
203
|
-
| [markdown-preferences/
|
|
204
|
-
| [markdown-preferences/
|
|
205
|
-
| [markdown-preferences/
|
|
206
|
-
| [markdown-preferences/no-
|
|
207
|
-
| [markdown-preferences/
|
|
208
|
-
| [markdown-preferences/
|
|
209
|
-
| [markdown-preferences/
|
|
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.
|
|
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-
|
|
10700
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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",
|