eslint-plugin-markdown-preferences 0.11.0 → 0.12.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 +3 -1
- package/lib/index.d.ts +14 -1
- package/lib/index.js +240 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -93,7 +93,9 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
93
93
|
| Rule ID | Description | Fixable | RECOMMENDED |
|
|
94
94
|
|:--------|:------------|:-------:|:-----------:|
|
|
95
95
|
| [markdown-preferences/canonical-code-block-language](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/canonical-code-block-language.html) | enforce canonical language names in code blocks | 🔧 | |
|
|
96
|
+
| [markdown-preferences/heading-casing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/heading-casing.html) | enforce consistent casing in headings. | 🔧 | |
|
|
96
97
|
| [markdown-preferences/no-text-backslash-linebreak](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-text-backslash-linebreak.html) | disallow text backslash at the end of a line. | | ⭐ |
|
|
98
|
+
| [markdown-preferences/ordered-list-marker-start](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-start.html) | enforce that ordered list markers start with 1 or 0 | 🔧 | |
|
|
97
99
|
| [markdown-preferences/prefer-inline-code-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html) | enforce the use of inline code for specific words. | 🔧 | |
|
|
98
100
|
| [markdown-preferences/prefer-linked-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html) | enforce the specified word to be a link. | 🔧 | |
|
|
99
101
|
|
|
@@ -103,10 +105,10 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
103
105
|
|:--------|:------------|:-------:|:-----------:|
|
|
104
106
|
| [markdown-preferences/definitions-last](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/definitions-last.html) | require link definitions and footnote definitions to be placed at the end of the document | 🔧 | |
|
|
105
107
|
| [markdown-preferences/hard-linebreak-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html) | enforce consistent hard linebreak style. | 🔧 | ⭐ |
|
|
106
|
-
| [markdown-preferences/heading-casing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/heading-casing.html) | enforce consistent casing in headings. | 🔧 | |
|
|
107
108
|
| [markdown-preferences/no-laziness-blockquotes](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-laziness-blockquotes.html) | disallow laziness in blockquotes | | ⭐ |
|
|
108
109
|
| [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. | 🔧 | |
|
|
109
110
|
| [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. | 🔧 | |
|
|
111
|
+
| [markdown-preferences/ordered-list-marker-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-sequence.html) | enforce that ordered list markers use sequential numbers | 🔧 | |
|
|
110
112
|
| [markdown-preferences/prefer-autolinks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html) | enforce the use of autolinks for URLs | 🔧 | ⭐ |
|
|
111
113
|
| [markdown-preferences/prefer-fenced-code-blocks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-fenced-code-blocks.html) | enforce the use of fenced code blocks over indented code blocks | 🔧 | ⭐ |
|
|
112
114
|
| [markdown-preferences/prefer-link-reference-definitions](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-link-reference-definitions.html) | enforce using link reference definitions instead of inline links | 🔧 | |
|
package/lib/index.d.ts
CHANGED
|
@@ -50,6 +50,16 @@ interface RuleOptions {
|
|
|
50
50
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-trailing-spaces.html
|
|
51
51
|
*/
|
|
52
52
|
'markdown-preferences/no-trailing-spaces'?: Linter.RuleEntry<MarkdownPreferencesNoTrailingSpaces>;
|
|
53
|
+
/**
|
|
54
|
+
* enforce that ordered list markers use sequential numbers
|
|
55
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-sequence.html
|
|
56
|
+
*/
|
|
57
|
+
'markdown-preferences/ordered-list-marker-sequence'?: Linter.RuleEntry<[]>;
|
|
58
|
+
/**
|
|
59
|
+
* enforce that ordered list markers start with 1 or 0
|
|
60
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-start.html
|
|
61
|
+
*/
|
|
62
|
+
'markdown-preferences/ordered-list-marker-start'?: Linter.RuleEntry<MarkdownPreferencesOrderedListMarkerStart>;
|
|
53
63
|
/**
|
|
54
64
|
* enforce the use of autolinks for URLs
|
|
55
65
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html
|
|
@@ -104,6 +114,9 @@ type MarkdownPreferencesNoTrailingSpaces = [] | [{
|
|
|
104
114
|
skipBlankLines?: boolean;
|
|
105
115
|
ignoreComments?: boolean;
|
|
106
116
|
}];
|
|
117
|
+
type MarkdownPreferencesOrderedListMarkerStart = [] | [{
|
|
118
|
+
start?: (1 | 0);
|
|
119
|
+
}];
|
|
107
120
|
type MarkdownPreferencesPreferInlineCodeWords = [] | [{
|
|
108
121
|
words: string[];
|
|
109
122
|
ignores?: {
|
|
@@ -156,7 +169,7 @@ declare namespace meta_d_exports {
|
|
|
156
169
|
export { name, version };
|
|
157
170
|
}
|
|
158
171
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
159
|
-
declare const version: "0.
|
|
172
|
+
declare const version: "0.12.0";
|
|
160
173
|
//#endregion
|
|
161
174
|
//#region src/index.d.ts
|
|
162
175
|
declare const configs: {
|
package/lib/index.js
CHANGED
|
@@ -822,7 +822,7 @@ var heading_casing_default = createRule("heading-casing", {
|
|
|
822
822
|
docs: {
|
|
823
823
|
description: "enforce consistent casing in headings.",
|
|
824
824
|
categories: [],
|
|
825
|
-
listCategory: "
|
|
825
|
+
listCategory: "Preference"
|
|
826
826
|
},
|
|
827
827
|
fixable: "code",
|
|
828
828
|
hasSuggestions: false,
|
|
@@ -1480,6 +1480,242 @@ function getLinkKind(sourceCode, node) {
|
|
|
1480
1480
|
const text = sourceCode.getText(node);
|
|
1481
1481
|
return text.startsWith("[") ? "inline" : text.startsWith("<") && text.endsWith(">") ? "autolink" : "gfm-autolink";
|
|
1482
1482
|
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Get the marker of a list item.
|
|
1485
|
+
*/
|
|
1486
|
+
function getListItemMarker(sourceCode, node) {
|
|
1487
|
+
const item = node.type === "list" ? node.children[0] || node : node;
|
|
1488
|
+
const text = sourceCode.getText(item);
|
|
1489
|
+
if (text.startsWith("-")) return {
|
|
1490
|
+
kind: "-",
|
|
1491
|
+
raw: "-"
|
|
1492
|
+
};
|
|
1493
|
+
if (text.startsWith("*")) return {
|
|
1494
|
+
kind: "*",
|
|
1495
|
+
raw: "*"
|
|
1496
|
+
};
|
|
1497
|
+
if (text.startsWith("+")) return {
|
|
1498
|
+
kind: "+",
|
|
1499
|
+
raw: "+"
|
|
1500
|
+
};
|
|
1501
|
+
const matchDot = /^(\d+)\./.exec(text);
|
|
1502
|
+
if (matchDot) return {
|
|
1503
|
+
kind: ".",
|
|
1504
|
+
raw: matchDot[0],
|
|
1505
|
+
sequence: Number(matchDot[1])
|
|
1506
|
+
};
|
|
1507
|
+
const matchParen = /^(\d+)\)/.exec(text);
|
|
1508
|
+
return {
|
|
1509
|
+
kind: ")",
|
|
1510
|
+
raw: matchParen[0],
|
|
1511
|
+
sequence: Number(matchParen[1])
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
//#endregion
|
|
1516
|
+
//#region src/rules/ordered-list-marker-sequence.ts
|
|
1517
|
+
var ordered_list_marker_sequence_default = createRule("ordered-list-marker-sequence", {
|
|
1518
|
+
meta: {
|
|
1519
|
+
type: "layout",
|
|
1520
|
+
docs: {
|
|
1521
|
+
description: "enforce that ordered list markers use sequential numbers",
|
|
1522
|
+
categories: [],
|
|
1523
|
+
listCategory: "Stylistic"
|
|
1524
|
+
},
|
|
1525
|
+
fixable: "code",
|
|
1526
|
+
hasSuggestions: true,
|
|
1527
|
+
schema: [],
|
|
1528
|
+
messages: {
|
|
1529
|
+
inconsistent: "Ordered list marker should be '{{expected}}' but found '{{actual}}'.",
|
|
1530
|
+
inconsistentStart: "Ordered list start should be {{expected}} but found '{{actual}}'.",
|
|
1531
|
+
suggestStartFromN: "Change list marker to start from '{{expected}}'."
|
|
1532
|
+
}
|
|
1533
|
+
},
|
|
1534
|
+
create(context) {
|
|
1535
|
+
const sourceCode = context.sourceCode;
|
|
1536
|
+
let scope = {
|
|
1537
|
+
node: sourceCode.ast,
|
|
1538
|
+
upper: null,
|
|
1539
|
+
last: null
|
|
1540
|
+
};
|
|
1541
|
+
/**
|
|
1542
|
+
* Verify the list start sequence.
|
|
1543
|
+
*/
|
|
1544
|
+
function verifyStartSequence(node, marker) {
|
|
1545
|
+
if (node.start == null || node.start <= 1) return true;
|
|
1546
|
+
if (scope.last != null && scope.last.sequence + 1 === node.start && marker.kind === scope.last.kind) return true;
|
|
1547
|
+
const expected = [0, 1];
|
|
1548
|
+
if (scope.last != null && marker.kind === scope.last.kind) expected.push(scope.last.sequence + 1);
|
|
1549
|
+
const suggest = [];
|
|
1550
|
+
for (const n of [...expected].sort((a, b) => Math.abs(node.start - a) - Math.abs(node.start - b))) suggest.push({
|
|
1551
|
+
messageId: "suggestStartFromN",
|
|
1552
|
+
data: { expected: String(n) },
|
|
1553
|
+
fix(fixer) {
|
|
1554
|
+
const expectedMarker = `${n}${marker.kind}`;
|
|
1555
|
+
const range = sourceCode.getRange(node);
|
|
1556
|
+
return fixer.replaceTextRange([range[0], range[0] + marker.raw.length], expectedMarker);
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
context.report({
|
|
1560
|
+
node: node.children[0] || node,
|
|
1561
|
+
messageId: "inconsistentStart",
|
|
1562
|
+
data: {
|
|
1563
|
+
expected: new Intl.ListFormat("en-US", { type: "disjunction" }).format(expected.map((n) => `'${n}'`)),
|
|
1564
|
+
actual: String(marker.sequence)
|
|
1565
|
+
},
|
|
1566
|
+
fix: scope.last == null ? (fixer) => {
|
|
1567
|
+
const expectedMarker = `1${marker.kind}`;
|
|
1568
|
+
const range = sourceCode.getRange(node);
|
|
1569
|
+
return fixer.replaceTextRange([range[0], range[0] + marker.raw.length], expectedMarker);
|
|
1570
|
+
} : null,
|
|
1571
|
+
suggest
|
|
1572
|
+
});
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Verify the list item markers.
|
|
1577
|
+
*/
|
|
1578
|
+
function verifyListItems(node) {
|
|
1579
|
+
if (node.start == null) return;
|
|
1580
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1581
|
+
const item = node.children[i];
|
|
1582
|
+
const marker = getListItemMarker(sourceCode, item);
|
|
1583
|
+
if (marker.kind !== "." && marker.kind !== ")") continue;
|
|
1584
|
+
const expectedSequence = node.start + i;
|
|
1585
|
+
if (marker.sequence !== expectedSequence) {
|
|
1586
|
+
const expectedMarker = `${expectedSequence}${marker.kind}`;
|
|
1587
|
+
context.report({
|
|
1588
|
+
node: item,
|
|
1589
|
+
messageId: "inconsistent",
|
|
1590
|
+
data: {
|
|
1591
|
+
expected: expectedMarker,
|
|
1592
|
+
actual: marker.raw
|
|
1593
|
+
},
|
|
1594
|
+
fix(fixer) {
|
|
1595
|
+
const range = sourceCode.getRange(item);
|
|
1596
|
+
return fixer.replaceTextRange([range[0], range[0] + marker.raw.length], expectedMarker);
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return {
|
|
1603
|
+
"blockquote, listItem, footnoteDefinition"(node) {
|
|
1604
|
+
scope = {
|
|
1605
|
+
node,
|
|
1606
|
+
upper: scope,
|
|
1607
|
+
last: null
|
|
1608
|
+
};
|
|
1609
|
+
},
|
|
1610
|
+
"blockquote, listItem, footnoteDefinition:exit"(node) {
|
|
1611
|
+
if (scope.node === node) scope = scope.upper;
|
|
1612
|
+
},
|
|
1613
|
+
heading() {
|
|
1614
|
+
scope.last = null;
|
|
1615
|
+
},
|
|
1616
|
+
list(node) {
|
|
1617
|
+
if (!node.ordered || node.start == null) return;
|
|
1618
|
+
const marker = getListItemMarker(sourceCode, node);
|
|
1619
|
+
if (marker.kind !== "." && marker.kind !== ")") return;
|
|
1620
|
+
verifyStartSequence(node, marker);
|
|
1621
|
+
verifyListItems(node);
|
|
1622
|
+
scope.last = {
|
|
1623
|
+
kind: marker.kind,
|
|
1624
|
+
sequence: node.start + node.children.length - 1
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
//#endregion
|
|
1632
|
+
//#region src/rules/ordered-list-marker-start.ts
|
|
1633
|
+
var ordered_list_marker_start_default = createRule("ordered-list-marker-start", {
|
|
1634
|
+
meta: {
|
|
1635
|
+
type: "layout",
|
|
1636
|
+
docs: {
|
|
1637
|
+
description: "enforce that ordered list markers start with 1 or 0",
|
|
1638
|
+
categories: [],
|
|
1639
|
+
listCategory: "Preference"
|
|
1640
|
+
},
|
|
1641
|
+
fixable: "code",
|
|
1642
|
+
hasSuggestions: true,
|
|
1643
|
+
schema: [{
|
|
1644
|
+
type: "object",
|
|
1645
|
+
properties: { start: { enum: [1, 0] } },
|
|
1646
|
+
additionalProperties: false
|
|
1647
|
+
}],
|
|
1648
|
+
messages: {
|
|
1649
|
+
wrongStart: "Ordered list should start with {{expected}} but found '{{actual}}'.",
|
|
1650
|
+
suggestStartFromN: "Change list marker to start from '{{expected}}'."
|
|
1651
|
+
}
|
|
1652
|
+
},
|
|
1653
|
+
create(context) {
|
|
1654
|
+
const sourceCode = context.sourceCode;
|
|
1655
|
+
const option = context.options[0] || {};
|
|
1656
|
+
const expectedStart = option.start ?? 1;
|
|
1657
|
+
let scope = {
|
|
1658
|
+
node: sourceCode.ast,
|
|
1659
|
+
upper: null,
|
|
1660
|
+
last: null
|
|
1661
|
+
};
|
|
1662
|
+
/**
|
|
1663
|
+
* Verify the list start sequence.
|
|
1664
|
+
*/
|
|
1665
|
+
function verifyStartSequence(node, marker) {
|
|
1666
|
+
if (node.start == null || node.start === expectedStart) return;
|
|
1667
|
+
if (scope.last != null && scope.last.sequence + 1 === node.start && marker.kind === scope.last.kind) return;
|
|
1668
|
+
const expected = [expectedStart];
|
|
1669
|
+
if (scope.last != null && marker.kind === scope.last.kind) expected.push(scope.last.sequence + 1);
|
|
1670
|
+
if (expected.includes(node.start)) return;
|
|
1671
|
+
const suggest = [];
|
|
1672
|
+
for (const n of [...expected].sort((a, b) => Math.abs(node.start - a) - Math.abs(node.start - b))) suggest.push({
|
|
1673
|
+
messageId: "suggestStartFromN",
|
|
1674
|
+
data: { expected: String(n) },
|
|
1675
|
+
fix(fixer) {
|
|
1676
|
+
const expectedMarker = `${n}${marker.kind}`;
|
|
1677
|
+
const range = sourceCode.getRange(node);
|
|
1678
|
+
return fixer.replaceTextRange([range[0], range[0] + marker.raw.length], expectedMarker);
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
context.report({
|
|
1682
|
+
node: node.children[0] || node,
|
|
1683
|
+
messageId: "wrongStart",
|
|
1684
|
+
data: {
|
|
1685
|
+
expected: new Intl.ListFormat("en-US", { type: "disjunction" }).format(expected.map((n) => `'${n}'`)),
|
|
1686
|
+
actual: String(node.start)
|
|
1687
|
+
},
|
|
1688
|
+
fix: suggest.length === 1 ? suggest[0].fix : void 0,
|
|
1689
|
+
suggest: suggest.length === 1 ? [] : suggest
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
return {
|
|
1693
|
+
"blockquote, listItem, footnoteDefinition"(node) {
|
|
1694
|
+
scope = {
|
|
1695
|
+
node,
|
|
1696
|
+
upper: scope,
|
|
1697
|
+
last: null
|
|
1698
|
+
};
|
|
1699
|
+
},
|
|
1700
|
+
"blockquote, listItem, footnoteDefinition:exit"(node) {
|
|
1701
|
+
if (scope.node === node) scope = scope.upper;
|
|
1702
|
+
},
|
|
1703
|
+
heading() {
|
|
1704
|
+
scope.last = null;
|
|
1705
|
+
},
|
|
1706
|
+
list(node) {
|
|
1707
|
+
if (!node.ordered || node.start == null) return;
|
|
1708
|
+
const marker = getListItemMarker(sourceCode, node);
|
|
1709
|
+
if (marker.kind !== "." && marker.kind !== ")") return;
|
|
1710
|
+
verifyStartSequence(node, marker);
|
|
1711
|
+
scope.last = {
|
|
1712
|
+
kind: marker.kind,
|
|
1713
|
+
sequence: node.start + node.children.length - 1
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1483
1719
|
|
|
1484
1720
|
//#endregion
|
|
1485
1721
|
//#region src/rules/prefer-autolinks.ts
|
|
@@ -2337,6 +2573,8 @@ const rules$1 = [
|
|
|
2337
2573
|
no_multiple_empty_lines_default,
|
|
2338
2574
|
no_text_backslash_linebreak_default,
|
|
2339
2575
|
no_trailing_spaces_default,
|
|
2576
|
+
ordered_list_marker_sequence_default,
|
|
2577
|
+
ordered_list_marker_start_default,
|
|
2340
2578
|
prefer_autolinks_default,
|
|
2341
2579
|
prefer_fenced_code_blocks_default,
|
|
2342
2580
|
prefer_inline_code_words_default,
|
|
@@ -2382,7 +2620,7 @@ __export(meta_exports, {
|
|
|
2382
2620
|
version: () => version
|
|
2383
2621
|
});
|
|
2384
2622
|
const name = "eslint-plugin-markdown-preferences";
|
|
2385
|
-
const version = "0.
|
|
2623
|
+
const version = "0.12.0";
|
|
2386
2624
|
|
|
2387
2625
|
//#endregion
|
|
2388
2626
|
//#region src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-markdown-preferences",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "ESLint plugin that enforces our markdown preferences",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"eslint-config-prettier": "^10.1.1",
|
|
84
84
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
85
85
|
"eslint-plugin-eslint-plugin": "^7.0.0",
|
|
86
|
-
"eslint-plugin-jsdoc": "^
|
|
86
|
+
"eslint-plugin-jsdoc": "^54.0.0",
|
|
87
87
|
"eslint-plugin-json-schema-validator": "^5.3.1",
|
|
88
88
|
"eslint-plugin-jsonc": "^2.19.1",
|
|
89
89
|
"eslint-plugin-markdown": "^5.1.0",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"stylelint-config-recommended-vue": "^1.6.0",
|
|
108
108
|
"stylelint-config-standard": "^39.0.0",
|
|
109
109
|
"stylelint-config-standard-vue": "^1.0.0",
|
|
110
|
-
"tsdown": "^0.
|
|
110
|
+
"tsdown": "^0.14.0",
|
|
111
111
|
"tsx": "^4.19.3",
|
|
112
112
|
"twoslash-eslint": "^0.3.1",
|
|
113
113
|
"type-fest": "^4.37.0",
|