eslint-plugin-markdown-preferences 0.10.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 +7 -3
- package/lib/index.d.ts +29 -3
- package/lib/index.js +385 -5
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -92,7 +92,10 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
92
92
|
|
|
93
93
|
| Rule ID | Description | Fixable | RECOMMENDED |
|
|
94
94
|
|:--------|:------------|:-------:|:-----------:|
|
|
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. | 🔧 | |
|
|
95
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 | 🔧 | |
|
|
96
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. | 🔧 | |
|
|
97
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. | 🔧 | |
|
|
98
101
|
|
|
@@ -100,13 +103,14 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
100
103
|
|
|
101
104
|
| Rule ID | Description | Fixable | RECOMMENDED |
|
|
102
105
|
|:--------|:------------|:-------:|:-----------:|
|
|
103
|
-
| [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 | 🔧 | |
|
|
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 | 🔧 | |
|
|
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 | 🔧 | ⭐ |
|
|
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 | 🔧 | ⭐ |
|
|
110
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 | 🔧 | |
|
|
111
115
|
| [markdown-preferences/sort-definitions](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/sort-definitions.html) | enforce a specific order for link definitions and footnote definitions | 🔧 | |
|
|
112
116
|
|
|
@@ -129,7 +133,7 @@ Please use GitHub's Issues/PRs.
|
|
|
129
133
|
|
|
130
134
|
## 🔒 License
|
|
131
135
|
|
|
132
|
-
See the [LICENSE](LICENSE) file for license rights and limitations (MIT).
|
|
136
|
+
See the [LICENSE](./LICENSE) file for license rights and limitations (MIT).
|
|
133
137
|
|
|
134
138
|
[npm-package]: https://www.npmjs.com/package/eslint-plugin-markdown-preferences
|
|
135
139
|
[npmtrends]: http://www.npmtrends.com/eslint-plugin-markdown-preferences
|
package/lib/index.d.ts
CHANGED
|
@@ -50,6 +50,26 @@ 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>;
|
|
63
|
+
/**
|
|
64
|
+
* enforce the use of autolinks for URLs
|
|
65
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html
|
|
66
|
+
*/
|
|
67
|
+
'markdown-preferences/prefer-autolinks'?: Linter.RuleEntry<[]>;
|
|
68
|
+
/**
|
|
69
|
+
* enforce the use of fenced code blocks over indented code blocks
|
|
70
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-fenced-code-blocks.html
|
|
71
|
+
*/
|
|
72
|
+
'markdown-preferences/prefer-fenced-code-blocks'?: Linter.RuleEntry<[]>;
|
|
53
73
|
/**
|
|
54
74
|
* enforce the use of inline code for specific words.
|
|
55
75
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html
|
|
@@ -94,6 +114,9 @@ type MarkdownPreferencesNoTrailingSpaces = [] | [{
|
|
|
94
114
|
skipBlankLines?: boolean;
|
|
95
115
|
ignoreComments?: boolean;
|
|
96
116
|
}];
|
|
117
|
+
type MarkdownPreferencesOrderedListMarkerStart = [] | [{
|
|
118
|
+
start?: (1 | 0);
|
|
119
|
+
}];
|
|
97
120
|
type MarkdownPreferencesPreferInlineCodeWords = [] | [{
|
|
98
121
|
words: string[];
|
|
99
122
|
ignores?: {
|
|
@@ -129,11 +152,14 @@ type MarkdownPreferencesSortDefinitions = [] | [{
|
|
|
129
152
|
alphabetical?: boolean;
|
|
130
153
|
}];
|
|
131
154
|
declare namespace recommended_d_exports {
|
|
132
|
-
export { files, language, name$1 as name, plugins, rules$1 as rules };
|
|
155
|
+
export { files, language, languageOptions, name$1 as name, plugins, rules$1 as rules };
|
|
133
156
|
}
|
|
134
157
|
declare const name$1 = "markdown-preferences/recommended";
|
|
135
158
|
declare const files: string[];
|
|
136
|
-
declare const language = "markdown/
|
|
159
|
+
declare const language = "markdown/gfm";
|
|
160
|
+
declare const languageOptions: {
|
|
161
|
+
frontmatter: string;
|
|
162
|
+
};
|
|
137
163
|
declare const plugins: {
|
|
138
164
|
markdown: typeof markdown;
|
|
139
165
|
readonly "markdown-preferences": ESLint.Plugin;
|
|
@@ -143,7 +169,7 @@ declare namespace meta_d_exports {
|
|
|
143
169
|
export { name, version };
|
|
144
170
|
}
|
|
145
171
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
146
|
-
declare const version: "0.
|
|
172
|
+
declare const version: "0.12.0";
|
|
147
173
|
//#endregion
|
|
148
174
|
//#region src/index.d.ts
|
|
149
175
|
declare const configs: {
|
package/lib/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var canonical_code_block_language_default = createRule("canonical-code-block-lan
|
|
|
56
56
|
docs: {
|
|
57
57
|
description: "enforce canonical language names in code blocks",
|
|
58
58
|
categories: [],
|
|
59
|
-
listCategory: "
|
|
59
|
+
listCategory: "Preference"
|
|
60
60
|
},
|
|
61
61
|
fixable: "code",
|
|
62
62
|
hasSuggestions: false,
|
|
@@ -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,
|
|
@@ -1464,6 +1464,378 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
|
|
|
1464
1464
|
}
|
|
1465
1465
|
});
|
|
1466
1466
|
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/utils/ast.ts
|
|
1469
|
+
/**
|
|
1470
|
+
* Get the kind of code block.
|
|
1471
|
+
*/
|
|
1472
|
+
function getCodeBlockKind(sourceCode, node) {
|
|
1473
|
+
const text = sourceCode.getText(node);
|
|
1474
|
+
return text.startsWith("```") ? "backtick-fenced" : text.startsWith("~~~") ? "tilde-fenced" : "indented";
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Get the kind of link.
|
|
1478
|
+
*/
|
|
1479
|
+
function getLinkKind(sourceCode, node) {
|
|
1480
|
+
const text = sourceCode.getText(node);
|
|
1481
|
+
return text.startsWith("[") ? "inline" : text.startsWith("<") && text.endsWith(">") ? "autolink" : "gfm-autolink";
|
|
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
|
+
});
|
|
1719
|
+
|
|
1720
|
+
//#endregion
|
|
1721
|
+
//#region src/rules/prefer-autolinks.ts
|
|
1722
|
+
var prefer_autolinks_default = createRule("prefer-autolinks", {
|
|
1723
|
+
meta: {
|
|
1724
|
+
type: "layout",
|
|
1725
|
+
docs: {
|
|
1726
|
+
description: "enforce the use of autolinks for URLs",
|
|
1727
|
+
categories: ["recommended"],
|
|
1728
|
+
listCategory: "Stylistic"
|
|
1729
|
+
},
|
|
1730
|
+
fixable: "code",
|
|
1731
|
+
hasSuggestions: false,
|
|
1732
|
+
schema: [],
|
|
1733
|
+
messages: {
|
|
1734
|
+
useAutolinkInsteadGFMAutolink: "Use an autolink instead of a GFM autolink (bare url).",
|
|
1735
|
+
useAutolinkInsteadInlineLink: "Use an autolink instead of an inline link with the same URL."
|
|
1736
|
+
}
|
|
1737
|
+
},
|
|
1738
|
+
create(context) {
|
|
1739
|
+
const sourceCode = context.sourceCode;
|
|
1740
|
+
return { link(node) {
|
|
1741
|
+
const kind = getLinkKind(sourceCode, node);
|
|
1742
|
+
if (kind === "autolink") return;
|
|
1743
|
+
if (node.title) return;
|
|
1744
|
+
if (node.children.some((child) => child.type !== "text")) return;
|
|
1745
|
+
const label = node.children.map((child) => child.value).join("");
|
|
1746
|
+
if (node.url !== label) return;
|
|
1747
|
+
context.report({
|
|
1748
|
+
node,
|
|
1749
|
+
messageId: kind === "inline" ? "useAutolinkInsteadInlineLink" : "useAutolinkInsteadGFMAutolink",
|
|
1750
|
+
fix(fixer) {
|
|
1751
|
+
return fixer.replaceText(node, `<${node.url}>`);
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
} };
|
|
1755
|
+
}
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
//#endregion
|
|
1759
|
+
//#region src/rules/prefer-fenced-code-blocks.ts
|
|
1760
|
+
var prefer_fenced_code_blocks_default = createRule("prefer-fenced-code-blocks", {
|
|
1761
|
+
meta: {
|
|
1762
|
+
type: "layout",
|
|
1763
|
+
docs: {
|
|
1764
|
+
description: "enforce the use of fenced code blocks over indented code blocks",
|
|
1765
|
+
categories: ["recommended"],
|
|
1766
|
+
listCategory: "Stylistic"
|
|
1767
|
+
},
|
|
1768
|
+
fixable: "code",
|
|
1769
|
+
hasSuggestions: false,
|
|
1770
|
+
schema: [],
|
|
1771
|
+
messages: { useFencedCodeBlock: "Use a fenced code block instead of an indented code block." }
|
|
1772
|
+
},
|
|
1773
|
+
create(context) {
|
|
1774
|
+
const sourceCode = context.sourceCode;
|
|
1775
|
+
const lines = parseLines(sourceCode);
|
|
1776
|
+
return { code(node) {
|
|
1777
|
+
const kind = getCodeBlockKind(sourceCode, node);
|
|
1778
|
+
if (kind === "backtick-fenced" || kind === "tilde-fenced") return;
|
|
1779
|
+
const loc = sourceCode.getLoc(node);
|
|
1780
|
+
context.report({
|
|
1781
|
+
node,
|
|
1782
|
+
messageId: "useFencedCodeBlock",
|
|
1783
|
+
fix(fixer) {
|
|
1784
|
+
if (!isFixableIndentedCodeBlock(node)) return null;
|
|
1785
|
+
const startColumnOffset = loc.start.column - 1;
|
|
1786
|
+
const removeRanges = [];
|
|
1787
|
+
let prefixText = null;
|
|
1788
|
+
for (let line = loc.start.line; line <= loc.end.line; line++) {
|
|
1789
|
+
const parsedLine = lines.get(line);
|
|
1790
|
+
const currentPrefix = normalizePrefix(parsedLine.text.slice(0, startColumnOffset));
|
|
1791
|
+
if (!prefixText) prefixText = currentPrefix;
|
|
1792
|
+
else if (currentPrefix !== prefixText) return null;
|
|
1793
|
+
let removeRange = [parsedLine.range[0] + startColumnOffset, parsedLine.range[0] + startColumnOffset + 4];
|
|
1794
|
+
for (let index = removeRange[0]; index < removeRange[1]; index++) {
|
|
1795
|
+
const c = sourceCode.text[index];
|
|
1796
|
+
if (c === " ") continue;
|
|
1797
|
+
if (c === " ") {
|
|
1798
|
+
removeRange = [removeRange[0], index + 1];
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
return null;
|
|
1802
|
+
}
|
|
1803
|
+
removeRanges.push(removeRange);
|
|
1804
|
+
}
|
|
1805
|
+
const beginFenceInsertOffset = lines.get(loc.start.line).range[0];
|
|
1806
|
+
const endFenceInsertOffset = lines.get(loc.end.line).range[1];
|
|
1807
|
+
return [
|
|
1808
|
+
fixer.insertTextBeforeRange([beginFenceInsertOffset, beginFenceInsertOffset], `${prefixText}\`\`\`\n`),
|
|
1809
|
+
...removeRanges.map((removeRange) => fixer.removeRange(removeRange)),
|
|
1810
|
+
fixer.insertTextAfterRange([endFenceInsertOffset, endFenceInsertOffset], `${prefixText}\`\`\`\n`)
|
|
1811
|
+
];
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
1814
|
+
} };
|
|
1815
|
+
/**
|
|
1816
|
+
* Determines whether the given indented code block is fixable or not.
|
|
1817
|
+
*/
|
|
1818
|
+
function isFixableIndentedCodeBlock(node) {
|
|
1819
|
+
if (!node.value.startsWith(" ")) return true;
|
|
1820
|
+
const loc = sourceCode.getLoc(node);
|
|
1821
|
+
const firstLine = lines.get(loc.start.line);
|
|
1822
|
+
const codeBlockFirstLine = normalizePrefix(node.value.split(/\r?\n/u)[0]);
|
|
1823
|
+
const startColumnOffset = loc.start.column - 1;
|
|
1824
|
+
const normalizedFirstLine = normalizePrefix(firstLine.text.slice(startColumnOffset));
|
|
1825
|
+
return normalizedFirstLine.startsWith(codeBlockFirstLine);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
/**
|
|
1830
|
+
* Normalize the prefix by removing tabs and replacing them with spaces.
|
|
1831
|
+
*/
|
|
1832
|
+
function normalizePrefix(prefix) {
|
|
1833
|
+
let result = "";
|
|
1834
|
+
for (const c of prefix) if (c !== " ") result += c;
|
|
1835
|
+
else result += " ".repeat(4 - result.length % 4);
|
|
1836
|
+
return result;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1467
1839
|
//#endregion
|
|
1468
1840
|
//#region src/utils/search-words.ts
|
|
1469
1841
|
const RE_BOUNDARY = /^[\s\p{Letter_Number}\p{Modifier_Letter}\p{Modifier_Symbol}\p{Nonspacing_Mark}\p{Other_Letter}\p{Other_Symbol}\p{Script=Han}!"#$%&'()*+,./:;<=>?\\{|}~\u{2ffc}-\u{303d}\u{30a0}-\u{30fb}\u{3192}-\u{32bf}\u{fe10}-\u{fe1f}\u{fe30}-\u{fe6f}\u{ff00}-\u{ffef}\u{2ebf0}-\u{2ee5d}]*$/u;
|
|
@@ -2201,6 +2573,10 @@ const rules$1 = [
|
|
|
2201
2573
|
no_multiple_empty_lines_default,
|
|
2202
2574
|
no_text_backslash_linebreak_default,
|
|
2203
2575
|
no_trailing_spaces_default,
|
|
2576
|
+
ordered_list_marker_sequence_default,
|
|
2577
|
+
ordered_list_marker_start_default,
|
|
2578
|
+
prefer_autolinks_default,
|
|
2579
|
+
prefer_fenced_code_blocks_default,
|
|
2204
2580
|
prefer_inline_code_words_default,
|
|
2205
2581
|
prefer_link_reference_definitions_default,
|
|
2206
2582
|
prefer_linked_words_default,
|
|
@@ -2213,13 +2589,15 @@ var recommended_exports = {};
|
|
|
2213
2589
|
__export(recommended_exports, {
|
|
2214
2590
|
files: () => files,
|
|
2215
2591
|
language: () => language,
|
|
2592
|
+
languageOptions: () => languageOptions,
|
|
2216
2593
|
name: () => name$1,
|
|
2217
2594
|
plugins: () => plugins,
|
|
2218
2595
|
rules: () => rules$2
|
|
2219
2596
|
});
|
|
2220
2597
|
const name$1 = "markdown-preferences/recommended";
|
|
2221
2598
|
const files = ["*.md", "**/*.md"];
|
|
2222
|
-
const language = "markdown/
|
|
2599
|
+
const language = "markdown/gfm";
|
|
2600
|
+
const languageOptions = { frontmatter: "yaml" };
|
|
2223
2601
|
const plugins = {
|
|
2224
2602
|
markdown,
|
|
2225
2603
|
get "markdown-preferences"() {
|
|
@@ -2229,7 +2607,9 @@ const plugins = {
|
|
|
2229
2607
|
const rules$2 = {
|
|
2230
2608
|
"markdown-preferences/hard-linebreak-style": "error",
|
|
2231
2609
|
"markdown-preferences/no-laziness-blockquotes": "error",
|
|
2232
|
-
"markdown-preferences/no-text-backslash-linebreak": "error"
|
|
2610
|
+
"markdown-preferences/no-text-backslash-linebreak": "error",
|
|
2611
|
+
"markdown-preferences/prefer-autolinks": "error",
|
|
2612
|
+
"markdown-preferences/prefer-fenced-code-blocks": "error"
|
|
2233
2613
|
};
|
|
2234
2614
|
|
|
2235
2615
|
//#endregion
|
|
@@ -2240,7 +2620,7 @@ __export(meta_exports, {
|
|
|
2240
2620
|
version: () => version
|
|
2241
2621
|
});
|
|
2242
2622
|
const name = "eslint-plugin-markdown-preferences";
|
|
2243
|
-
const version = "0.
|
|
2623
|
+
const version = "0.12.0";
|
|
2244
2624
|
|
|
2245
2625
|
//#endregion
|
|
2246
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",
|