eslint-plugin-markdown-preferences 0.15.0 → 0.16.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 +15 -2
- package/lib/index.d.ts +17 -1
- package/lib/index.js +366 -80
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ npm install --save-dev eslint @eslint/markdown eslint-plugin-markdown-preference
|
|
|
37
37
|
## 📖 Usage
|
|
38
38
|
|
|
39
39
|
<!--USAGE_SECTION_START-->
|
|
40
|
+
|
|
40
41
|
<!--USAGE_GUIDE_START-->
|
|
41
42
|
|
|
42
43
|
### Configuration
|
|
@@ -75,6 +76,7 @@ See [the rule list](https://ota-meshi.github.io/eslint-plugin-markdown-preferenc
|
|
|
75
76
|
Is not supported.
|
|
76
77
|
|
|
77
78
|
<!--USAGE_GUIDE_END-->
|
|
79
|
+
|
|
78
80
|
<!--USAGE_SECTION_END-->
|
|
79
81
|
|
|
80
82
|
## ✅ Rules
|
|
@@ -86,12 +88,12 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
86
88
|
|
|
87
89
|
<!--RULES_TABLE_START-->
|
|
88
90
|
|
|
89
|
-
<!-- prettier-ignore-start -->
|
|
90
|
-
|
|
91
91
|
### Preference Rules
|
|
92
92
|
|
|
93
93
|
- Rules to unify the expression and description style of documents.
|
|
94
94
|
|
|
95
|
+
<!-- prettier-ignore-start -->
|
|
96
|
+
|
|
95
97
|
| Rule ID | Description | Fixable | RECOMMENDED |
|
|
96
98
|
|:--------|:------------|:-------:|:-----------:|
|
|
97
99
|
| [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 | 🔧 | |
|
|
@@ -102,10 +104,14 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
102
104
|
| [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. | 🔧 | |
|
|
103
105
|
| [markdown-preferences/table-header-casing](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/table-header-casing.html) | enforce consistent casing in table header cells. | 🔧 | |
|
|
104
106
|
|
|
107
|
+
<!-- prettier-ignore-end -->
|
|
108
|
+
|
|
105
109
|
### Stylistic Rules
|
|
106
110
|
|
|
107
111
|
- Rules related to the formatting and visual style of Markdown.
|
|
108
112
|
|
|
113
|
+
<!-- prettier-ignore-start -->
|
|
114
|
+
|
|
109
115
|
| Rule ID | Description | Fixable | RECOMMENDED |
|
|
110
116
|
|:--------|:------------|:-------:|:-----------:|
|
|
111
117
|
| [markdown-preferences/atx-headings-closing-sequence-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-headings-closing-sequence-length.html) | enforce consistent length for the closing sequence (trailing #s) in ATX headings. | 🔧 | |
|
|
@@ -119,6 +125,7 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
119
125
|
| [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. | | ⭐ |
|
|
120
126
|
| [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. | 🔧 | |
|
|
121
127
|
| [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 | 🔧 | |
|
|
128
|
+
| [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 | 🔧 | ⭐ |
|
|
122
129
|
| [markdown-preferences/prefer-autolinks](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html) | enforce the use of autolinks for URLs | 🔧 | ⭐ |
|
|
123
130
|
| [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 | 🔧 | ⭐ |
|
|
124
131
|
| [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 | 🔧 | |
|
|
@@ -127,7 +134,13 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
127
134
|
<!-- prettier-ignore-end -->
|
|
128
135
|
|
|
129
136
|
<!--RULES_TABLE_END-->
|
|
137
|
+
|
|
130
138
|
<!--RULES_SECTION_END-->
|
|
139
|
+
|
|
140
|
+
## 👫 Related Packages
|
|
141
|
+
|
|
142
|
+
- [eslint-plugin-markdown-links](https://github.com/ota-meshi/eslint-plugin-markdown-links) ... ESLint plugin with powerful checking rules related to Markdown links.
|
|
143
|
+
|
|
131
144
|
<!--DOCS_IGNORE_START-->
|
|
132
145
|
|
|
133
146
|
## 🍻 Contributing
|
package/lib/index.d.ts
CHANGED
|
@@ -85,6 +85,11 @@ interface RuleOptions {
|
|
|
85
85
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-start.html
|
|
86
86
|
*/
|
|
87
87
|
'markdown-preferences/ordered-list-marker-start'?: Linter.RuleEntry<MarkdownPreferencesOrderedListMarkerStart>;
|
|
88
|
+
/**
|
|
89
|
+
* require or disallow padding lines between blocks
|
|
90
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/padding-line-between-blocks.html
|
|
91
|
+
*/
|
|
92
|
+
'markdown-preferences/padding-line-between-blocks'?: Linter.RuleEntry<MarkdownPreferencesPaddingLineBetweenBlocks>;
|
|
88
93
|
/**
|
|
89
94
|
* enforce the use of autolinks for URLs
|
|
90
95
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html
|
|
@@ -162,6 +167,17 @@ type MarkdownPreferencesNoTrailingSpaces = [] | [{
|
|
|
162
167
|
type MarkdownPreferencesOrderedListMarkerStart = [] | [{
|
|
163
168
|
start?: (1 | 0);
|
|
164
169
|
}];
|
|
170
|
+
type MarkdownPreferencesPaddingLineBetweenBlocks = {
|
|
171
|
+
prev: (("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*") | [("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"), ...(("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"))[]] | {
|
|
172
|
+
type: (("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*") | [("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"), ...(("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"))[]]);
|
|
173
|
+
in?: ("list" | "blockquote" | "footnote-definition");
|
|
174
|
+
});
|
|
175
|
+
next: (("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*") | [("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"), ...(("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"))[]] | {
|
|
176
|
+
type: (("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*") | [("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"), ...(("blockquote" | "code" | "heading" | "html" | "list" | "paragraph" | "thematic-break" | "table" | "link-definition" | "footnote-definition" | "frontmatter" | "*"))[]]);
|
|
177
|
+
in?: ("list" | "blockquote" | "footnote-definition");
|
|
178
|
+
});
|
|
179
|
+
blankLine: ("any" | "never" | "always");
|
|
180
|
+
}[];
|
|
165
181
|
type MarkdownPreferencesPreferInlineCodeWords = [] | [{
|
|
166
182
|
words: string[];
|
|
167
183
|
ignores?: {
|
|
@@ -220,7 +236,7 @@ declare namespace meta_d_exports {
|
|
|
220
236
|
export { name, version };
|
|
221
237
|
}
|
|
222
238
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
223
|
-
declare const version: "0.
|
|
239
|
+
declare const version: "0.16.0";
|
|
224
240
|
//#endregion
|
|
225
241
|
//#region src/index.d.ts
|
|
226
242
|
declare const configs: {
|
package/lib/index.js
CHANGED
|
@@ -27,14 +27,110 @@ function createRule(ruleName, rule) {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/utils/ast.ts
|
|
32
|
+
/**
|
|
33
|
+
* Get the kind of heading.
|
|
34
|
+
*/
|
|
35
|
+
function getHeadingKind(sourceCode, node) {
|
|
36
|
+
const loc = sourceCode.getLoc(node);
|
|
37
|
+
if (loc.start.line !== loc.end.line) return "setext";
|
|
38
|
+
return "atx";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get the kind of code block.
|
|
42
|
+
*/
|
|
43
|
+
function getCodeBlockKind(sourceCode, node) {
|
|
44
|
+
const text = sourceCode.getText(node);
|
|
45
|
+
return text.startsWith("```") ? "backtick-fenced" : text.startsWith("~~~") ? "tilde-fenced" : "indented";
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the kind of link.
|
|
49
|
+
*/
|
|
50
|
+
function getLinkKind(sourceCode, node) {
|
|
51
|
+
const text = sourceCode.getText(node);
|
|
52
|
+
return text.startsWith("[") ? "inline" : text.startsWith("<") && text.endsWith(">") ? "autolink" : "gfm-autolink";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the marker of a list item.
|
|
56
|
+
*/
|
|
57
|
+
function getListItemMarker(sourceCode, node) {
|
|
58
|
+
const item = node.type === "list" ? node.children[0] || node : node;
|
|
59
|
+
const text = sourceCode.getText(item);
|
|
60
|
+
if (text.startsWith("-")) return {
|
|
61
|
+
kind: "-",
|
|
62
|
+
raw: "-"
|
|
63
|
+
};
|
|
64
|
+
if (text.startsWith("*")) return {
|
|
65
|
+
kind: "*",
|
|
66
|
+
raw: "*"
|
|
67
|
+
};
|
|
68
|
+
if (text.startsWith("+")) return {
|
|
69
|
+
kind: "+",
|
|
70
|
+
raw: "+"
|
|
71
|
+
};
|
|
72
|
+
const matchDot = /^(\d+)\./.exec(text);
|
|
73
|
+
if (matchDot) return {
|
|
74
|
+
kind: ".",
|
|
75
|
+
raw: matchDot[0],
|
|
76
|
+
sequence: Number(matchDot[1])
|
|
77
|
+
};
|
|
78
|
+
const matchParen = /^(\d+)\)/.exec(text);
|
|
79
|
+
return {
|
|
80
|
+
kind: ")",
|
|
81
|
+
raw: matchParen[0],
|
|
82
|
+
sequence: Number(matchParen[1])
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get the marker for a thematic break.
|
|
87
|
+
*/
|
|
88
|
+
function getThematicBreakMarker(sourceCode, node) {
|
|
89
|
+
const text = sourceCode.getText(node);
|
|
90
|
+
return {
|
|
91
|
+
kind: text.startsWith("-") ? "-" : "*",
|
|
92
|
+
hasSpaces: /\s/u.test(text)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the source location from a range in a node.
|
|
97
|
+
*/
|
|
98
|
+
function getSourceLocationFromRange(sourceCode, node, range) {
|
|
99
|
+
const [nodeStart] = sourceCode.getRange(node);
|
|
100
|
+
let startLine, startColumn;
|
|
101
|
+
if (nodeStart <= range[0]) {
|
|
102
|
+
const loc = sourceCode.getLoc(node);
|
|
103
|
+
const beforeLines = sourceCode.text.slice(nodeStart, range[0]).split(/\n/u);
|
|
104
|
+
startLine = loc.start.line + beforeLines.length - 1;
|
|
105
|
+
startColumn = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
|
|
106
|
+
} else {
|
|
107
|
+
const beforeLines = sourceCode.text.slice(0, range[0]).split(/\n/u);
|
|
108
|
+
startLine = beforeLines.length;
|
|
109
|
+
startColumn = 1 + (beforeLines.at(-1) || "").length;
|
|
110
|
+
}
|
|
111
|
+
const contentLines = sourceCode.text.slice(range[0], range[1]).split(/\n/u);
|
|
112
|
+
const endLine = startLine + contentLines.length - 1;
|
|
113
|
+
const endColumn = (contentLines.length === 1 ? startColumn : 1) + (contentLines.at(-1) || "").length;
|
|
114
|
+
return {
|
|
115
|
+
start: {
|
|
116
|
+
line: startLine,
|
|
117
|
+
column: startColumn
|
|
118
|
+
},
|
|
119
|
+
end: {
|
|
120
|
+
line: endLine,
|
|
121
|
+
column: endColumn
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
30
126
|
//#endregion
|
|
31
127
|
//#region src/utils/atx-heading.ts
|
|
32
128
|
/**
|
|
33
129
|
* Parse the closing sequence of an ATX heading.
|
|
34
130
|
*/
|
|
35
131
|
function parseATXHeadingClosingSequence(sourceCode, node) {
|
|
132
|
+
if (getHeadingKind(sourceCode, node) !== "atx") return null;
|
|
36
133
|
const loc = sourceCode.getLoc(node);
|
|
37
|
-
if (loc.start.line !== loc.end.line) return null;
|
|
38
134
|
const range = sourceCode.getRange(node);
|
|
39
135
|
const parsed = parseATXHeadingClosingSequenceFromText(sourceCode.text.slice(...range));
|
|
40
136
|
if (parsed == null) return { closingSequence: null };
|
|
@@ -497,84 +593,6 @@ var blockquote_marker_alignment_default = createRule("blockquote-marker-alignmen
|
|
|
497
593
|
}
|
|
498
594
|
});
|
|
499
595
|
|
|
500
|
-
//#endregion
|
|
501
|
-
//#region src/utils/ast.ts
|
|
502
|
-
/**
|
|
503
|
-
* Get the kind of code block.
|
|
504
|
-
*/
|
|
505
|
-
function getCodeBlockKind(sourceCode, node) {
|
|
506
|
-
const text = sourceCode.getText(node);
|
|
507
|
-
return text.startsWith("```") ? "backtick-fenced" : text.startsWith("~~~") ? "tilde-fenced" : "indented";
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Get the kind of link.
|
|
511
|
-
*/
|
|
512
|
-
function getLinkKind(sourceCode, node) {
|
|
513
|
-
const text = sourceCode.getText(node);
|
|
514
|
-
return text.startsWith("[") ? "inline" : text.startsWith("<") && text.endsWith(">") ? "autolink" : "gfm-autolink";
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Get the marker of a list item.
|
|
518
|
-
*/
|
|
519
|
-
function getListItemMarker(sourceCode, node) {
|
|
520
|
-
const item = node.type === "list" ? node.children[0] || node : node;
|
|
521
|
-
const text = sourceCode.getText(item);
|
|
522
|
-
if (text.startsWith("-")) return {
|
|
523
|
-
kind: "-",
|
|
524
|
-
raw: "-"
|
|
525
|
-
};
|
|
526
|
-
if (text.startsWith("*")) return {
|
|
527
|
-
kind: "*",
|
|
528
|
-
raw: "*"
|
|
529
|
-
};
|
|
530
|
-
if (text.startsWith("+")) return {
|
|
531
|
-
kind: "+",
|
|
532
|
-
raw: "+"
|
|
533
|
-
};
|
|
534
|
-
const matchDot = /^(\d+)\./.exec(text);
|
|
535
|
-
if (matchDot) return {
|
|
536
|
-
kind: ".",
|
|
537
|
-
raw: matchDot[0],
|
|
538
|
-
sequence: Number(matchDot[1])
|
|
539
|
-
};
|
|
540
|
-
const matchParen = /^(\d+)\)/.exec(text);
|
|
541
|
-
return {
|
|
542
|
-
kind: ")",
|
|
543
|
-
raw: matchParen[0],
|
|
544
|
-
sequence: Number(matchParen[1])
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* Get the source location from a range in a node.
|
|
549
|
-
*/
|
|
550
|
-
function getSourceLocationFromRange(sourceCode, node, range) {
|
|
551
|
-
const [nodeStart] = sourceCode.getRange(node);
|
|
552
|
-
let startLine, startColumn;
|
|
553
|
-
if (nodeStart <= range[0]) {
|
|
554
|
-
const loc = sourceCode.getLoc(node);
|
|
555
|
-
const beforeLines = sourceCode.text.slice(nodeStart, range[0]).split(/\n/u);
|
|
556
|
-
startLine = loc.start.line + beforeLines.length - 1;
|
|
557
|
-
startColumn = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
|
|
558
|
-
} else {
|
|
559
|
-
const beforeLines = sourceCode.text.slice(0, range[0]).split(/\n/u);
|
|
560
|
-
startLine = beforeLines.length;
|
|
561
|
-
startColumn = 1 + (beforeLines.at(-1) || "").length;
|
|
562
|
-
}
|
|
563
|
-
const contentLines = sourceCode.text.slice(range[0], range[1]).split(/\n/u);
|
|
564
|
-
const endLine = startLine + contentLines.length - 1;
|
|
565
|
-
const endColumn = (contentLines.length === 1 ? startColumn : 1) + (contentLines.at(-1) || "").length;
|
|
566
|
-
return {
|
|
567
|
-
start: {
|
|
568
|
-
line: startLine,
|
|
569
|
-
column: startColumn
|
|
570
|
-
},
|
|
571
|
-
end: {
|
|
572
|
-
line: endLine,
|
|
573
|
-
column: endColumn
|
|
574
|
-
}
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
|
|
578
596
|
//#endregion
|
|
579
597
|
//#region src/rules/canonical-code-block-language.ts
|
|
580
598
|
const DEFAULT_LANGUAGES = {
|
|
@@ -4284,6 +4302,272 @@ var ordered_list_marker_start_default = createRule("ordered-list-marker-start",
|
|
|
4284
4302
|
}
|
|
4285
4303
|
});
|
|
4286
4304
|
|
|
4305
|
+
//#endregion
|
|
4306
|
+
//#region src/rules/padding-line-between-blocks.ts
|
|
4307
|
+
/**
|
|
4308
|
+
* Determines whether a blank line must be preserved between two nodes
|
|
4309
|
+
* due to Markdown syntax constraints (e.g., setext headings).
|
|
4310
|
+
*/
|
|
4311
|
+
function requiresBlankLineBetween(prev, next, sourceCode) {
|
|
4312
|
+
if (prev.type === "paragraph") {
|
|
4313
|
+
if (next.type === "paragraph" || next.type === "definition") return true;
|
|
4314
|
+
else if (next.type === "heading") return getHeadingKind(sourceCode, next) === "setext";
|
|
4315
|
+
else if (next.type === "thematicBreak") {
|
|
4316
|
+
const marker = getThematicBreakMarker(sourceCode, next);
|
|
4317
|
+
return marker.kind === "-" && !marker.hasSpaces;
|
|
4318
|
+
}
|
|
4319
|
+
} else if (prev.type === "list") {
|
|
4320
|
+
if (next.type === "paragraph" || next.type === "table" || next.type === "definition") return true;
|
|
4321
|
+
else if (next.type === "heading") return getHeadingKind(sourceCode, next) === "setext";
|
|
4322
|
+
} else if (prev.type === "blockquote") {
|
|
4323
|
+
if (next.type === "paragraph" || next.type === "blockquote" || next.type === "table" || next.type === "definition") return true;
|
|
4324
|
+
else if (next.type === "heading") return getHeadingKind(sourceCode, next) === "setext";
|
|
4325
|
+
} else if (prev.type === "table") {
|
|
4326
|
+
if (next.type === "paragraph" || next.type === "table" || next.type === "definition") return true;
|
|
4327
|
+
else if (next.type === "heading") return getHeadingKind(sourceCode, next) === "setext";
|
|
4328
|
+
} else if (prev.type === "footnoteDefinition") {
|
|
4329
|
+
if (next.type === "paragraph" || next.type === "table" || next.type === "definition") return true;
|
|
4330
|
+
else if (next.type === "heading") return getHeadingKind(sourceCode, next) === "setext";
|
|
4331
|
+
} else if (prev.type === "html") return true;
|
|
4332
|
+
return false;
|
|
4333
|
+
}
|
|
4334
|
+
const BLOCK_TYPES = [
|
|
4335
|
+
"blockquote",
|
|
4336
|
+
"code",
|
|
4337
|
+
"heading",
|
|
4338
|
+
"html",
|
|
4339
|
+
"list",
|
|
4340
|
+
"paragraph",
|
|
4341
|
+
"thematic-break",
|
|
4342
|
+
"table",
|
|
4343
|
+
"link-definition",
|
|
4344
|
+
"footnote-definition",
|
|
4345
|
+
"frontmatter",
|
|
4346
|
+
"*"
|
|
4347
|
+
];
|
|
4348
|
+
const BLOCK_TYPE_SCHEMAS = [{
|
|
4349
|
+
type: "string",
|
|
4350
|
+
enum: BLOCK_TYPES
|
|
4351
|
+
}, {
|
|
4352
|
+
type: "array",
|
|
4353
|
+
items: {
|
|
4354
|
+
type: "string",
|
|
4355
|
+
enum: BLOCK_TYPES
|
|
4356
|
+
},
|
|
4357
|
+
minItems: 1
|
|
4358
|
+
}];
|
|
4359
|
+
const SELECTOR_SCHEMA = { oneOf: [...BLOCK_TYPE_SCHEMAS, {
|
|
4360
|
+
type: "object",
|
|
4361
|
+
properties: {
|
|
4362
|
+
type: { oneOf: BLOCK_TYPE_SCHEMAS },
|
|
4363
|
+
in: { enum: [
|
|
4364
|
+
"list",
|
|
4365
|
+
"blockquote",
|
|
4366
|
+
"footnote-definition"
|
|
4367
|
+
] }
|
|
4368
|
+
},
|
|
4369
|
+
required: ["type"],
|
|
4370
|
+
additionalProperties: false
|
|
4371
|
+
}] };
|
|
4372
|
+
const BLOCK_TYPE_MAP0 = {
|
|
4373
|
+
heading: "heading",
|
|
4374
|
+
paragraph: "paragraph",
|
|
4375
|
+
list: "list",
|
|
4376
|
+
blockquote: "blockquote",
|
|
4377
|
+
code: "code",
|
|
4378
|
+
html: "html",
|
|
4379
|
+
table: "table",
|
|
4380
|
+
thematicBreak: "thematic-break",
|
|
4381
|
+
definition: "link-definition",
|
|
4382
|
+
footnoteDefinition: "footnote-definition",
|
|
4383
|
+
json: "frontmatter",
|
|
4384
|
+
toml: "frontmatter",
|
|
4385
|
+
yaml: "frontmatter"
|
|
4386
|
+
};
|
|
4387
|
+
const BLOCK_TYPE_MAP = BLOCK_TYPE_MAP0;
|
|
4388
|
+
/**
|
|
4389
|
+
* Get the block type of a node
|
|
4390
|
+
*/
|
|
4391
|
+
function getBlockType(node) {
|
|
4392
|
+
const nodeType = node.type;
|
|
4393
|
+
const blockType = BLOCK_TYPE_MAP[nodeType];
|
|
4394
|
+
if (blockType) return blockType;
|
|
4395
|
+
return null;
|
|
4396
|
+
}
|
|
4397
|
+
var padding_line_between_blocks_default = createRule("padding-line-between-blocks", {
|
|
4398
|
+
meta: {
|
|
4399
|
+
type: "layout",
|
|
4400
|
+
docs: {
|
|
4401
|
+
description: "require or disallow padding lines between blocks",
|
|
4402
|
+
categories: ["recommended"],
|
|
4403
|
+
listCategory: "Stylistic"
|
|
4404
|
+
},
|
|
4405
|
+
fixable: "whitespace",
|
|
4406
|
+
hasSuggestions: false,
|
|
4407
|
+
schema: {
|
|
4408
|
+
type: "array",
|
|
4409
|
+
items: {
|
|
4410
|
+
type: "object",
|
|
4411
|
+
properties: {
|
|
4412
|
+
prev: SELECTOR_SCHEMA,
|
|
4413
|
+
next: SELECTOR_SCHEMA,
|
|
4414
|
+
blankLine: {
|
|
4415
|
+
type: "string",
|
|
4416
|
+
enum: [
|
|
4417
|
+
"any",
|
|
4418
|
+
"never",
|
|
4419
|
+
"always"
|
|
4420
|
+
]
|
|
4421
|
+
}
|
|
4422
|
+
},
|
|
4423
|
+
required: [
|
|
4424
|
+
"prev",
|
|
4425
|
+
"next",
|
|
4426
|
+
"blankLine"
|
|
4427
|
+
],
|
|
4428
|
+
additionalProperties: false
|
|
4429
|
+
}
|
|
4430
|
+
},
|
|
4431
|
+
messages: {
|
|
4432
|
+
expectedBlankLine: "Expected a blank line between {{prevType}} and {{nextType}}.",
|
|
4433
|
+
unexpectedBlankLine: "Unexpected blank line between {{prevType}} and {{nextType}}."
|
|
4434
|
+
}
|
|
4435
|
+
},
|
|
4436
|
+
create(context) {
|
|
4437
|
+
const sourceCode = context.sourceCode;
|
|
4438
|
+
const options = [...context.options || []].reverse();
|
|
4439
|
+
const containerStack = [];
|
|
4440
|
+
/**
|
|
4441
|
+
* Check if the actual type matches the expected type pattern
|
|
4442
|
+
*/
|
|
4443
|
+
function matchesType(actualType, block, expected) {
|
|
4444
|
+
if (Array.isArray(expected)) {
|
|
4445
|
+
for (const e of expected) {
|
|
4446
|
+
const matched$1 = matchesType(actualType, block, e);
|
|
4447
|
+
if (matched$1) return matched$1;
|
|
4448
|
+
}
|
|
4449
|
+
return null;
|
|
4450
|
+
}
|
|
4451
|
+
if (typeof expected === "string") return expected === actualType || expected === "*" ? actualType : null;
|
|
4452
|
+
let matched = null;
|
|
4453
|
+
if (Array.isArray(expected.type)) for (const e of expected.type) {
|
|
4454
|
+
matched = matchesType(actualType, block, e);
|
|
4455
|
+
if (matched) break;
|
|
4456
|
+
}
|
|
4457
|
+
else matched = matchesType(actualType, block, expected.type);
|
|
4458
|
+
if (!matched) return null;
|
|
4459
|
+
if (expected.in === "list") {
|
|
4460
|
+
if (containerStack[0]?.type !== "listItem") return null;
|
|
4461
|
+
} else if (expected.in === "blockquote") {
|
|
4462
|
+
if (containerStack[0]?.type !== "blockquote") return null;
|
|
4463
|
+
} else if (expected.in === "footnote-definition") {
|
|
4464
|
+
if (containerStack[0]?.type !== "footnoteDefinition") return null;
|
|
4465
|
+
}
|
|
4466
|
+
return matched;
|
|
4467
|
+
}
|
|
4468
|
+
/**
|
|
4469
|
+
* Get the expected padding between two blocks
|
|
4470
|
+
*/
|
|
4471
|
+
function getExpectedPadding(prevBlock, nextBlock) {
|
|
4472
|
+
const prevType = getBlockType(prevBlock);
|
|
4473
|
+
const nextType = getBlockType(nextBlock);
|
|
4474
|
+
if (!prevType || !nextType) return null;
|
|
4475
|
+
for (const rule of options) {
|
|
4476
|
+
const prev = matchesType(prevType, prevBlock, rule.prev);
|
|
4477
|
+
if (!prev) continue;
|
|
4478
|
+
const next = matchesType(nextType, nextBlock, rule.next);
|
|
4479
|
+
if (!next) continue;
|
|
4480
|
+
return {
|
|
4481
|
+
prev,
|
|
4482
|
+
next,
|
|
4483
|
+
blankLine: rule.blankLine
|
|
4484
|
+
};
|
|
4485
|
+
}
|
|
4486
|
+
return null;
|
|
4487
|
+
}
|
|
4488
|
+
/**
|
|
4489
|
+
* Check padding between blocks in a container node
|
|
4490
|
+
*/
|
|
4491
|
+
function checkBlockPadding(containerNode) {
|
|
4492
|
+
for (let i = 0; i < containerNode.children.length - 1; i++) {
|
|
4493
|
+
const prevBlock = containerNode.children[i];
|
|
4494
|
+
const nextBlock = containerNode.children[i + 1];
|
|
4495
|
+
const expected = getExpectedPadding(prevBlock, nextBlock);
|
|
4496
|
+
if (expected === null) continue;
|
|
4497
|
+
const prevLoc = sourceCode.getLoc(prevBlock);
|
|
4498
|
+
const nextLoc = sourceCode.getLoc(nextBlock);
|
|
4499
|
+
const actualBlankLine = nextLoc.start.line - prevLoc.end.line - 1;
|
|
4500
|
+
const hasBlankLine = actualBlankLine > 0;
|
|
4501
|
+
let messageId = "expectedBlankLine";
|
|
4502
|
+
if (expected.blankLine === "always") {
|
|
4503
|
+
if (hasBlankLine) continue;
|
|
4504
|
+
let list = null;
|
|
4505
|
+
const stack$1 = [...containerStack];
|
|
4506
|
+
let target$1;
|
|
4507
|
+
while (target$1 = stack$1.shift()) if (target$1.type === "listItem") {
|
|
4508
|
+
list = target$1;
|
|
4509
|
+
break;
|
|
4510
|
+
}
|
|
4511
|
+
if (list && !list.spread) continue;
|
|
4512
|
+
messageId = "expectedBlankLine";
|
|
4513
|
+
} else if (expected.blankLine === "never") {
|
|
4514
|
+
if (!hasBlankLine) continue;
|
|
4515
|
+
if (requiresBlankLineBetween(prevBlock, nextBlock, sourceCode)) continue;
|
|
4516
|
+
messageId = "unexpectedBlankLine";
|
|
4517
|
+
} else continue;
|
|
4518
|
+
const lineLength = sourceCode.lines[nextLoc.start.line - 1].length;
|
|
4519
|
+
let blockquote = null;
|
|
4520
|
+
const stack = [...containerStack];
|
|
4521
|
+
let target;
|
|
4522
|
+
while (target = stack.shift()) if (target.type === "blockquote") {
|
|
4523
|
+
blockquote = target;
|
|
4524
|
+
break;
|
|
4525
|
+
}
|
|
4526
|
+
context.report({
|
|
4527
|
+
node: nextBlock,
|
|
4528
|
+
loc: {
|
|
4529
|
+
start: nextLoc.start,
|
|
4530
|
+
end: {
|
|
4531
|
+
line: nextLoc.start.line,
|
|
4532
|
+
column: lineLength + 1
|
|
4533
|
+
}
|
|
4534
|
+
},
|
|
4535
|
+
messageId,
|
|
4536
|
+
data: {
|
|
4537
|
+
prevType: expected.prev,
|
|
4538
|
+
nextType: expected.next
|
|
4539
|
+
},
|
|
4540
|
+
fix(fixer) {
|
|
4541
|
+
if (expected.blankLine === "always") {
|
|
4542
|
+
let text = "\n";
|
|
4543
|
+
if (blockquote) {
|
|
4544
|
+
const blockquoteLoc = sourceCode.getLoc(blockquote);
|
|
4545
|
+
text += getBlockquoteLevelFromLine(sourceCode, blockquoteLoc.start.line).prefix.trimEnd();
|
|
4546
|
+
}
|
|
4547
|
+
const nextRange = sourceCode.getRange(nextBlock);
|
|
4548
|
+
const startNext = nextRange[0] - nextLoc.start.column;
|
|
4549
|
+
return fixer.insertTextBeforeRange([startNext, startNext], text);
|
|
4550
|
+
}
|
|
4551
|
+
const lines = getParsedLines(sourceCode);
|
|
4552
|
+
const linesToRemove = [];
|
|
4553
|
+
for (let line = prevLoc.end.line + 1; line < nextLoc.start.line; line++) linesToRemove.push(lines.get(line));
|
|
4554
|
+
return linesToRemove.map((line) => fixer.removeRange(line.range));
|
|
4555
|
+
}
|
|
4556
|
+
});
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
return {
|
|
4560
|
+
"root, blockquote, listItem, footnoteDefinition"(node) {
|
|
4561
|
+
containerStack.unshift(node);
|
|
4562
|
+
},
|
|
4563
|
+
"root, blockquote, listItem, footnoteDefinition:exit"(node) {
|
|
4564
|
+
checkBlockPadding(node);
|
|
4565
|
+
containerStack.shift();
|
|
4566
|
+
}
|
|
4567
|
+
};
|
|
4568
|
+
}
|
|
4569
|
+
});
|
|
4570
|
+
|
|
4287
4571
|
//#endregion
|
|
4288
4572
|
//#region src/rules/prefer-autolinks.ts
|
|
4289
4573
|
var prefer_autolinks_default = createRule("prefer-autolinks", {
|
|
@@ -5270,6 +5554,7 @@ const rules$1 = [
|
|
|
5270
5554
|
no_trailing_spaces_default,
|
|
5271
5555
|
ordered_list_marker_sequence_default,
|
|
5272
5556
|
ordered_list_marker_start_default,
|
|
5557
|
+
padding_line_between_blocks_default,
|
|
5273
5558
|
prefer_autolinks_default,
|
|
5274
5559
|
prefer_fenced_code_blocks_default,
|
|
5275
5560
|
prefer_inline_code_words_default,
|
|
@@ -5306,6 +5591,7 @@ const rules$2 = {
|
|
|
5306
5591
|
"markdown-preferences/list-marker-alignment": "error",
|
|
5307
5592
|
"markdown-preferences/no-laziness-blockquotes": "error",
|
|
5308
5593
|
"markdown-preferences/no-text-backslash-linebreak": "error",
|
|
5594
|
+
"markdown-preferences/padding-line-between-blocks": "error",
|
|
5309
5595
|
"markdown-preferences/prefer-autolinks": "error",
|
|
5310
5596
|
"markdown-preferences/prefer-fenced-code-blocks": "error"
|
|
5311
5597
|
};
|
|
@@ -5318,7 +5604,7 @@ __export(meta_exports, {
|
|
|
5318
5604
|
version: () => version
|
|
5319
5605
|
});
|
|
5320
5606
|
const name = "eslint-plugin-markdown-preferences";
|
|
5321
|
-
const version = "0.
|
|
5607
|
+
const version = "0.16.0";
|
|
5322
5608
|
|
|
5323
5609
|
//#endregion
|
|
5324
5610
|
//#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.16.0",
|
|
4
4
|
"description": "ESLint plugin that enforces our markdown preferences",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"docs:build": "vitepress build docs",
|
|
34
34
|
"ts": "node --import=tsx",
|
|
35
35
|
"mocha": "npm run ts -- ./node_modules/mocha/bin/mocha.js",
|
|
36
|
-
"generate:version": "env-cmd -e version npm run update && npm run lint -- --fix",
|
|
36
|
+
"generate:version": "env-cmd -e version -- npm run update && npm run lint -- --fix",
|
|
37
37
|
"changeset:version": "changeset version && npm run generate:version && git add --all",
|
|
38
38
|
"changeset:publish": "npm run build && changeset publish"
|
|
39
39
|
},
|
|
@@ -62,14 +62,14 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"emoji-regex-xs": "^2.0.1",
|
|
65
|
-
"string-width": "^
|
|
65
|
+
"string-width": "^8.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@changesets/changelog-github": "^0.5.1",
|
|
69
69
|
"@changesets/cli": "^2.28.1",
|
|
70
70
|
"@changesets/get-release-plan": "^4.0.8",
|
|
71
71
|
"@eslint/core": "^0.15.0",
|
|
72
|
-
"@eslint/markdown": "^7.
|
|
72
|
+
"@eslint/markdown": "^7.2.0",
|
|
73
73
|
"@ota-meshi/eslint-plugin": "^0.18.0",
|
|
74
74
|
"@shikijs/vitepress-twoslash": "^3.0.0",
|
|
75
75
|
"@types/eslint": "^9.6.1",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"@types/semver": "^7.5.8",
|
|
84
84
|
"assert": "^2.1.0",
|
|
85
85
|
"c8": "^10.1.3",
|
|
86
|
-
"env-cmd": "^
|
|
87
|
-
"eslint": "^9.
|
|
86
|
+
"env-cmd": "^11.0.0",
|
|
87
|
+
"eslint": "^9.34.0",
|
|
88
88
|
"eslint-compat-utils": "^0.6.4",
|
|
89
89
|
"eslint-config-prettier": "^10.1.1",
|
|
90
90
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"eslint-plugin-json-schema-validator": "^5.3.1",
|
|
94
94
|
"eslint-plugin-jsonc": "^2.19.1",
|
|
95
95
|
"eslint-plugin-markdown": "^5.1.0",
|
|
96
|
+
"eslint-plugin-markdown-links": "^0.4.0",
|
|
96
97
|
"eslint-plugin-n": "^17.16.2",
|
|
97
98
|
"eslint-plugin-node-dependencies": "^1.0.0",
|
|
98
99
|
"eslint-plugin-prettier": "^5.2.3",
|