eslint-plugin-markdown-preferences 0.14.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 +21 -6
- package/lib/index.d.ts +30 -1
- package/lib/index.js +603 -113
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -13,10 +13,10 @@ A specialized ESLint plugin that helps enforce consistent writing style and form
|
|
|
13
13
|
|
|
14
14
|
## 📛 Features
|
|
15
15
|
|
|
16
|
-
- **⚡ Effortless automation** - Transform your Markdown with auto-fixing that handles formatting,
|
|
17
|
-
- **📖 Professional documentation** - Enforce consistent
|
|
18
|
-
-
|
|
19
|
-
- **⚙️
|
|
16
|
+
- **⚡ Effortless automation** - Transform your Markdown with auto-fixing that handles formatting, casing, and style consistency automatically
|
|
17
|
+
- **📖 Professional documentation** - Enforce consistent headings, table headers, and organize link definitions for enterprise-ready documentation
|
|
18
|
+
- **🎨 Clean formatting** - Remove trailing spaces, control line breaks, standardize code blocks, and ensure consistent list numbering for polished output
|
|
19
|
+
- **⚙️ Flexible customization** - Configure casing styles (Title Case, Sentence case), code block languages, emoji notation, and more with extensive options
|
|
20
20
|
|
|
21
21
|
**Try it live:** Check out the [Online Demo](https://eslint-online-playground.netlify.app/#eslint-plugin-markdown-preferences) to see the plugin in action!
|
|
22
22
|
|
|
@@ -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,21 +104,28 @@ 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. | 🔧 | |
|
|
112
118
|
| [markdown-preferences/atx-headings-closing-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-headings-closing-sequence.html) | enforce consistent use of closing sequence in ATX headings. | 🔧 | |
|
|
119
|
+
| [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 | 🔧 | ⭐ |
|
|
113
120
|
| [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 | 🔧 | |
|
|
114
121
|
| [markdown-preferences/hard-linebreak-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html) | enforce consistent hard linebreak style. | 🔧 | ⭐ |
|
|
122
|
+
| [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 | 🔧 | ⭐ |
|
|
115
123
|
| [markdown-preferences/no-laziness-blockquotes](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-laziness-blockquotes.html) | disallow laziness in blockquotes | | ⭐ |
|
|
116
124
|
| [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. | 🔧 | |
|
|
117
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. | | ⭐ |
|
|
118
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. | 🔧 | |
|
|
119
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 | 🔧 | ⭐ |
|
|
120
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 | 🔧 | ⭐ |
|
|
121
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 | 🔧 | ⭐ |
|
|
122
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 | 🔧 | |
|
|
@@ -125,7 +134,13 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
125
134
|
<!-- prettier-ignore-end -->
|
|
126
135
|
|
|
127
136
|
<!--RULES_TABLE_END-->
|
|
137
|
+
|
|
128
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
|
+
|
|
129
144
|
<!--DOCS_IGNORE_START-->
|
|
130
145
|
|
|
131
146
|
## 🍻 Contributing
|
package/lib/index.d.ts
CHANGED
|
@@ -20,6 +20,11 @@ interface RuleOptions {
|
|
|
20
20
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-headings-closing-sequence-length.html
|
|
21
21
|
*/
|
|
22
22
|
'markdown-preferences/atx-headings-closing-sequence-length'?: Linter.RuleEntry<MarkdownPreferencesAtxHeadingsClosingSequenceLength>;
|
|
23
|
+
/**
|
|
24
|
+
* enforce consistent alignment of blockquote markers
|
|
25
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/blockquote-marker-alignment.html
|
|
26
|
+
*/
|
|
27
|
+
'markdown-preferences/blockquote-marker-alignment'?: Linter.RuleEntry<[]>;
|
|
23
28
|
/**
|
|
24
29
|
* enforce canonical language names in code blocks
|
|
25
30
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/canonical-code-block-language.html
|
|
@@ -45,6 +50,11 @@ interface RuleOptions {
|
|
|
45
50
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/heading-casing.html
|
|
46
51
|
*/
|
|
47
52
|
'markdown-preferences/heading-casing'?: Linter.RuleEntry<MarkdownPreferencesHeadingCasing>;
|
|
53
|
+
/**
|
|
54
|
+
* enforce consistent alignment of list markers
|
|
55
|
+
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html
|
|
56
|
+
*/
|
|
57
|
+
'markdown-preferences/list-marker-alignment'?: Linter.RuleEntry<MarkdownPreferencesListMarkerAlignment>;
|
|
48
58
|
/**
|
|
49
59
|
* disallow laziness in blockquotes
|
|
50
60
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-laziness-blockquotes.html
|
|
@@ -75,6 +85,11 @@ interface RuleOptions {
|
|
|
75
85
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-start.html
|
|
76
86
|
*/
|
|
77
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>;
|
|
78
93
|
/**
|
|
79
94
|
* enforce the use of autolinks for URLs
|
|
80
95
|
* @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-autolinks.html
|
|
@@ -137,6 +152,9 @@ type MarkdownPreferencesHeadingCasing = [] | [{
|
|
|
137
152
|
ignorePatterns?: string[];
|
|
138
153
|
minorWords?: string[];
|
|
139
154
|
}];
|
|
155
|
+
type MarkdownPreferencesListMarkerAlignment = [] | [{
|
|
156
|
+
align?: ("left" | "right");
|
|
157
|
+
}];
|
|
140
158
|
type MarkdownPreferencesNoMultipleEmptyLines = [] | [{
|
|
141
159
|
max?: number;
|
|
142
160
|
maxEOF?: number;
|
|
@@ -149,6 +167,17 @@ type MarkdownPreferencesNoTrailingSpaces = [] | [{
|
|
|
149
167
|
type MarkdownPreferencesOrderedListMarkerStart = [] | [{
|
|
150
168
|
start?: (1 | 0);
|
|
151
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
|
+
}[];
|
|
152
181
|
type MarkdownPreferencesPreferInlineCodeWords = [] | [{
|
|
153
182
|
words: string[];
|
|
154
183
|
ignores?: {
|
|
@@ -207,7 +236,7 @@ declare namespace meta_d_exports {
|
|
|
207
236
|
export { name, version };
|
|
208
237
|
}
|
|
209
238
|
declare const name: "eslint-plugin-markdown-preferences";
|
|
210
|
-
declare const version: "0.
|
|
239
|
+
declare const version: "0.16.0";
|
|
211
240
|
//#endregion
|
|
212
241
|
//#region src/index.d.ts
|
|
213
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 };
|
|
@@ -108,7 +204,7 @@ function parseATXHeadingClosingSequenceFromText(text) {
|
|
|
108
204
|
|
|
109
205
|
//#endregion
|
|
110
206
|
//#region src/utils/lines.ts
|
|
111
|
-
const cache = /* @__PURE__ */ new WeakMap();
|
|
207
|
+
const cache$1 = /* @__PURE__ */ new WeakMap();
|
|
112
208
|
var ParsedLines = class {
|
|
113
209
|
lines;
|
|
114
210
|
constructor(codeText) {
|
|
@@ -150,11 +246,11 @@ var ParsedLines = class {
|
|
|
150
246
|
* @param sourceCode source code to parse
|
|
151
247
|
* @returns parsed lines
|
|
152
248
|
*/
|
|
153
|
-
function
|
|
154
|
-
const cached = cache.get(sourceCode);
|
|
249
|
+
function getParsedLines(sourceCode) {
|
|
250
|
+
const cached = cache$1.get(sourceCode);
|
|
155
251
|
if (cached) return cached;
|
|
156
252
|
const parsedLines = new ParsedLines(sourceCode.text);
|
|
157
|
-
cache.set(sourceCode, parsedLines);
|
|
253
|
+
cache$1.set(sourceCode, parsedLines);
|
|
158
254
|
return parsedLines;
|
|
159
255
|
}
|
|
160
256
|
|
|
@@ -228,9 +324,8 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
|
|
|
228
324
|
} };
|
|
229
325
|
})() : option.mode === "fixed-line-length" ? (() => {
|
|
230
326
|
const totalLength = option.length || 80;
|
|
231
|
-
const lines = parseLines(sourceCode);
|
|
232
327
|
const getExpected = (_node, parsed) => {
|
|
233
|
-
return totalLength - getContentLength(
|
|
328
|
+
return totalLength - getContentLength(parsed);
|
|
234
329
|
};
|
|
235
330
|
return { heading(node) {
|
|
236
331
|
verifyATXHeadingClosingSequenceLength(node, getExpected);
|
|
@@ -246,7 +341,6 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
|
|
|
246
341
|
} else verifyATXHeadingClosingSequenceLength(node, getExpected);
|
|
247
342
|
} };
|
|
248
343
|
})() : option.mode === "consistent-line-length" ? (() => {
|
|
249
|
-
const lines = parseLines(sourceCode);
|
|
250
344
|
const headings = [];
|
|
251
345
|
return {
|
|
252
346
|
heading(node) {
|
|
@@ -260,9 +354,9 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
|
|
|
260
354
|
"root:exit"() {
|
|
261
355
|
let mostLongContentHeading = null;
|
|
262
356
|
for (const heading of headings) {
|
|
263
|
-
const contentLength = getContentLength(
|
|
357
|
+
const contentLength = getContentLength(heading.parsed);
|
|
264
358
|
if (mostLongContentHeading == null || contentLength > mostLongContentHeading.contentLength) {
|
|
265
|
-
const lineLength = getLineLength(
|
|
359
|
+
const lineLength = getLineLength(heading.parsed);
|
|
266
360
|
mostLongContentHeading = {
|
|
267
361
|
...heading,
|
|
268
362
|
contentLength,
|
|
@@ -273,11 +367,11 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
|
|
|
273
367
|
if (!mostLongContentHeading) return;
|
|
274
368
|
let minLineLength = mostLongContentHeading.lineLength;
|
|
275
369
|
for (const heading of headings) {
|
|
276
|
-
const lineLength = getLineLength(
|
|
370
|
+
const lineLength = getLineLength(heading.parsed);
|
|
277
371
|
if (mostLongContentHeading.contentLength < lineLength && lineLength < minLineLength) minLineLength = Math.min(minLineLength, lineLength);
|
|
278
372
|
}
|
|
279
373
|
const getExpected = (_node, parsed) => {
|
|
280
|
-
return minLineLength - getContentLength(
|
|
374
|
+
return minLineLength - getContentLength(parsed);
|
|
281
375
|
};
|
|
282
376
|
for (const { node } of headings) verifyATXHeadingClosingSequenceLength(node, getExpected);
|
|
283
377
|
}
|
|
@@ -286,7 +380,8 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
|
|
|
286
380
|
/**
|
|
287
381
|
* Get the content length of the heading.
|
|
288
382
|
*/
|
|
289
|
-
function getContentLength(
|
|
383
|
+
function getContentLength(parsed) {
|
|
384
|
+
const lines = getParsedLines(sourceCode);
|
|
290
385
|
const line = lines.get(parsed.closingSequence.loc.start.line);
|
|
291
386
|
const beforeClosing = sourceCode.text.slice(line.range[0], parsed.closingSequence.range[0]);
|
|
292
387
|
return getTextWidth(beforeClosing);
|
|
@@ -294,7 +389,8 @@ var atx_headings_closing_sequence_length_default = createRule("atx-headings-clos
|
|
|
294
389
|
/**
|
|
295
390
|
* Get the line length of the heading.
|
|
296
391
|
*/
|
|
297
|
-
function getLineLength(
|
|
392
|
+
function getLineLength(parsed) {
|
|
393
|
+
const lines = getParsedLines(sourceCode);
|
|
298
394
|
const line = lines.get(parsed.closingSequence.loc.start.line);
|
|
299
395
|
const lineText = sourceCode.text.slice(line.range[0], parsed.closingSequence.range[1]);
|
|
300
396
|
return getTextWidth(lineText);
|
|
@@ -381,83 +477,122 @@ var atx_headings_closing_sequence_default = createRule("atx-headings-closing-seq
|
|
|
381
477
|
});
|
|
382
478
|
|
|
383
479
|
//#endregion
|
|
384
|
-
//#region src/utils/
|
|
385
|
-
|
|
386
|
-
* Get the kind of code block.
|
|
387
|
-
*/
|
|
388
|
-
function getCodeBlockKind(sourceCode, node) {
|
|
389
|
-
const text = sourceCode.getText(node);
|
|
390
|
-
return text.startsWith("```") ? "backtick-fenced" : text.startsWith("~~~") ? "tilde-fenced" : "indented";
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Get the kind of link.
|
|
394
|
-
*/
|
|
395
|
-
function getLinkKind(sourceCode, node) {
|
|
396
|
-
const text = sourceCode.getText(node);
|
|
397
|
-
return text.startsWith("[") ? "inline" : text.startsWith("<") && text.endsWith(">") ? "autolink" : "gfm-autolink";
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Get the marker of a list item.
|
|
401
|
-
*/
|
|
402
|
-
function getListItemMarker(sourceCode, node) {
|
|
403
|
-
const item = node.type === "list" ? node.children[0] || node : node;
|
|
404
|
-
const text = sourceCode.getText(item);
|
|
405
|
-
if (text.startsWith("-")) return {
|
|
406
|
-
kind: "-",
|
|
407
|
-
raw: "-"
|
|
408
|
-
};
|
|
409
|
-
if (text.startsWith("*")) return {
|
|
410
|
-
kind: "*",
|
|
411
|
-
raw: "*"
|
|
412
|
-
};
|
|
413
|
-
if (text.startsWith("+")) return {
|
|
414
|
-
kind: "+",
|
|
415
|
-
raw: "+"
|
|
416
|
-
};
|
|
417
|
-
const matchDot = /^(\d+)\./.exec(text);
|
|
418
|
-
if (matchDot) return {
|
|
419
|
-
kind: ".",
|
|
420
|
-
raw: matchDot[0],
|
|
421
|
-
sequence: Number(matchDot[1])
|
|
422
|
-
};
|
|
423
|
-
const matchParen = /^(\d+)\)/.exec(text);
|
|
424
|
-
return {
|
|
425
|
-
kind: ")",
|
|
426
|
-
raw: matchParen[0],
|
|
427
|
-
sequence: Number(matchParen[1])
|
|
428
|
-
};
|
|
429
|
-
}
|
|
480
|
+
//#region src/utils/blockquotes.ts
|
|
481
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
430
482
|
/**
|
|
431
|
-
*
|
|
483
|
+
* Helper function to get blockquote level information.
|
|
432
484
|
*/
|
|
433
|
-
function
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const beforeLines = sourceCode.text.slice(nodeStart, range[0]).split(/\n/u);
|
|
439
|
-
startLine = loc.start.line + beforeLines.length - 1;
|
|
440
|
-
startColumn = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
|
|
441
|
-
} else {
|
|
442
|
-
const beforeLines = sourceCode.text.slice(0, range[0]).split(/\n/u);
|
|
443
|
-
startLine = beforeLines.length;
|
|
444
|
-
startColumn = 1 + (beforeLines.at(-1) || "").length;
|
|
485
|
+
function getBlockquoteLevelFromLine(sourceCode, lineNumber) {
|
|
486
|
+
let map = cache.get(sourceCode);
|
|
487
|
+
if (!map) {
|
|
488
|
+
map = /* @__PURE__ */ new Map();
|
|
489
|
+
cache.set(sourceCode, map);
|
|
445
490
|
}
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
491
|
+
const cached = map.get(lineNumber);
|
|
492
|
+
if (cached) return cached;
|
|
493
|
+
const lineText = sourceCode.lines[lineNumber - 1];
|
|
494
|
+
let prefix = "";
|
|
495
|
+
let level = 0;
|
|
496
|
+
const blockquoteMarkers = /* @__PURE__ */ new Map();
|
|
497
|
+
for (const c of lineText) {
|
|
498
|
+
if (c === ">") {
|
|
499
|
+
level++;
|
|
500
|
+
blockquoteMarkers.set(level, { index: prefix.length });
|
|
501
|
+
} else if (c.trim()) break;
|
|
502
|
+
prefix += c;
|
|
503
|
+
}
|
|
504
|
+
const result = {
|
|
505
|
+
line: lineNumber,
|
|
506
|
+
prefix,
|
|
507
|
+
level,
|
|
508
|
+
blockquoteMarkers
|
|
458
509
|
};
|
|
510
|
+
map.set(lineNumber, result);
|
|
511
|
+
return result;
|
|
459
512
|
}
|
|
460
513
|
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/rules/blockquote-marker-alignment.ts
|
|
516
|
+
var blockquote_marker_alignment_default = createRule("blockquote-marker-alignment", {
|
|
517
|
+
meta: {
|
|
518
|
+
type: "layout",
|
|
519
|
+
docs: {
|
|
520
|
+
description: "enforce consistent alignment of blockquote markers",
|
|
521
|
+
categories: ["recommended"],
|
|
522
|
+
listCategory: "Stylistic"
|
|
523
|
+
},
|
|
524
|
+
fixable: "whitespace",
|
|
525
|
+
hasSuggestions: false,
|
|
526
|
+
schema: [],
|
|
527
|
+
messages: { inconsistentAlignment: "Blockquote markers should be consistently aligned at the same nesting level." }
|
|
528
|
+
},
|
|
529
|
+
create(context) {
|
|
530
|
+
const sourceCode = context.sourceCode;
|
|
531
|
+
let blockquoteStack = {
|
|
532
|
+
node: sourceCode.ast,
|
|
533
|
+
level: 0,
|
|
534
|
+
upper: null
|
|
535
|
+
};
|
|
536
|
+
return {
|
|
537
|
+
blockquote(node) {
|
|
538
|
+
blockquoteStack = {
|
|
539
|
+
node,
|
|
540
|
+
level: blockquoteStack.level + 1,
|
|
541
|
+
upper: blockquoteStack,
|
|
542
|
+
reported: blockquoteStack.reported
|
|
543
|
+
};
|
|
544
|
+
if (blockquoteStack.reported) return;
|
|
545
|
+
const blockquoteLevel = blockquoteStack.level;
|
|
546
|
+
const loc = sourceCode.getLoc(node);
|
|
547
|
+
const startLine = loc.start.line;
|
|
548
|
+
const endLine = loc.end.line;
|
|
549
|
+
const base = getBlockquoteLevelFromLine(sourceCode, startLine).blockquoteMarkers.get(blockquoteLevel);
|
|
550
|
+
if (!base) return;
|
|
551
|
+
for (let lineNumber = startLine + 1; lineNumber <= endLine; lineNumber++) {
|
|
552
|
+
const marker = getBlockquoteLevelFromLine(sourceCode, lineNumber).blockquoteMarkers.get(blockquoteLevel);
|
|
553
|
+
if (!marker) continue;
|
|
554
|
+
if (base.index === marker.index) continue;
|
|
555
|
+
blockquoteStack.reported = true;
|
|
556
|
+
context.report({
|
|
557
|
+
node,
|
|
558
|
+
loc: {
|
|
559
|
+
start: {
|
|
560
|
+
line: lineNumber,
|
|
561
|
+
column: marker.index + 1
|
|
562
|
+
},
|
|
563
|
+
end: {
|
|
564
|
+
line: lineNumber,
|
|
565
|
+
column: marker.index + 2
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
messageId: "inconsistentAlignment",
|
|
569
|
+
fix(fixer) {
|
|
570
|
+
const lines = getParsedLines(sourceCode);
|
|
571
|
+
const line = lines.get(lineNumber);
|
|
572
|
+
if (marker.index < base.index) {
|
|
573
|
+
const addSpaces = " ".repeat(base.index - marker.index);
|
|
574
|
+
return fixer.insertTextBeforeRange([line.range[0] + marker.index, line.range[0] + marker.index], addSpaces);
|
|
575
|
+
}
|
|
576
|
+
if (blockquoteLevel === 1) {
|
|
577
|
+
const expectedSpaces = " ".repeat(base.index);
|
|
578
|
+
return fixer.replaceTextRange([line.range[0], line.range[0] + marker.index], expectedSpaces);
|
|
579
|
+
}
|
|
580
|
+
const itemBefore = line.text.slice(0, line.range[0] + marker.index);
|
|
581
|
+
if (itemBefore.includes(" ")) return null;
|
|
582
|
+
let removeStartIndex = marker.index;
|
|
583
|
+
for (; removeStartIndex > base.index; removeStartIndex--) if (line.text[removeStartIndex - 1] !== " ") break;
|
|
584
|
+
return fixer.removeRange([line.range[0] + removeStartIndex, line.range[0] + marker.index]);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
"blockquote:exit"() {
|
|
590
|
+
blockquoteStack = blockquoteStack.upper;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
461
596
|
//#endregion
|
|
462
597
|
//#region src/rules/canonical-code-block-language.ts
|
|
463
598
|
const DEFAULT_LANGUAGES = {
|
|
@@ -3458,20 +3593,99 @@ var heading_casing_default = createRule("heading-casing", {
|
|
|
3458
3593
|
}
|
|
3459
3594
|
});
|
|
3460
3595
|
|
|
3596
|
+
//#endregion
|
|
3597
|
+
//#region src/rules/list-marker-alignment.ts
|
|
3598
|
+
const ALIGN_TO_POSITION_NAME = {
|
|
3599
|
+
left: "start",
|
|
3600
|
+
right: "end"
|
|
3601
|
+
};
|
|
3602
|
+
var list_marker_alignment_default = createRule("list-marker-alignment", {
|
|
3603
|
+
meta: {
|
|
3604
|
+
type: "layout",
|
|
3605
|
+
docs: {
|
|
3606
|
+
description: "enforce consistent alignment of list markers",
|
|
3607
|
+
categories: ["recommended"],
|
|
3608
|
+
listCategory: "Stylistic"
|
|
3609
|
+
},
|
|
3610
|
+
fixable: "whitespace",
|
|
3611
|
+
hasSuggestions: false,
|
|
3612
|
+
schema: [{
|
|
3613
|
+
type: "object",
|
|
3614
|
+
properties: { align: { enum: ["left", "right"] } },
|
|
3615
|
+
additionalProperties: false
|
|
3616
|
+
}],
|
|
3617
|
+
messages: { incorrectAlignment: "List marker alignment is inconsistent. Expected {{expected}} characters of indentation, but got {{actual}}." }
|
|
3618
|
+
},
|
|
3619
|
+
create(context) {
|
|
3620
|
+
const sourceCode = context.sourceCode;
|
|
3621
|
+
const alignPositionName = ALIGN_TO_POSITION_NAME[context.options[0]?.align ?? "left"];
|
|
3622
|
+
/**
|
|
3623
|
+
* Get the marker location of a list item
|
|
3624
|
+
*/
|
|
3625
|
+
function getMarkerLocation(node) {
|
|
3626
|
+
const start = sourceCode.getLoc(node).start;
|
|
3627
|
+
const startColumnIndex = start.column - 1;
|
|
3628
|
+
const marker = getListItemMarker(sourceCode, node);
|
|
3629
|
+
return {
|
|
3630
|
+
line: start.line,
|
|
3631
|
+
start: startColumnIndex,
|
|
3632
|
+
end: startColumnIndex + marker.raw.length
|
|
3633
|
+
};
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3636
|
+
* Check if list items have consistent alignment
|
|
3637
|
+
*/
|
|
3638
|
+
function checkListAlignment(listNode) {
|
|
3639
|
+
const items = listNode.children;
|
|
3640
|
+
if (items.length <= 1) return;
|
|
3641
|
+
const referenceMarkerLocation = getMarkerLocation(items[0]);
|
|
3642
|
+
for (const item of items.slice(1)) {
|
|
3643
|
+
const markerLocation = getMarkerLocation(item);
|
|
3644
|
+
const diff = markerLocation[alignPositionName] - referenceMarkerLocation[alignPositionName];
|
|
3645
|
+
if (diff === 0) continue;
|
|
3646
|
+
context.report({
|
|
3647
|
+
node: item,
|
|
3648
|
+
loc: {
|
|
3649
|
+
start: {
|
|
3650
|
+
line: markerLocation.line,
|
|
3651
|
+
column: markerLocation.start + 1
|
|
3652
|
+
},
|
|
3653
|
+
end: {
|
|
3654
|
+
line: markerLocation.line,
|
|
3655
|
+
column: markerLocation.end + 1
|
|
3656
|
+
}
|
|
3657
|
+
},
|
|
3658
|
+
messageId: "incorrectAlignment",
|
|
3659
|
+
data: {
|
|
3660
|
+
expected: String(markerLocation.start - diff),
|
|
3661
|
+
actual: String(markerLocation.start)
|
|
3662
|
+
},
|
|
3663
|
+
fix(fixer) {
|
|
3664
|
+
const lines = getParsedLines(sourceCode);
|
|
3665
|
+
const line = lines.get(markerLocation.line);
|
|
3666
|
+
if (diff < 0) {
|
|
3667
|
+
const addSpaces = " ".repeat(-diff);
|
|
3668
|
+
return fixer.insertTextBeforeRange([line.range[0] + markerLocation.start, line.range[0] + markerLocation.start], addSpaces);
|
|
3669
|
+
}
|
|
3670
|
+
const itemBefore = line.text.slice(0, markerLocation.start - diff);
|
|
3671
|
+
if (itemBefore.includes(" ")) return null;
|
|
3672
|
+
const referenceMarkerBefore = lines.get(referenceMarkerLocation.line).text.slice(0, referenceMarkerLocation.start);
|
|
3673
|
+
if (referenceMarkerBefore === itemBefore) {
|
|
3674
|
+
const removeEndIndex = line.range[0] + markerLocation.start;
|
|
3675
|
+
const removeStartIndex = removeEndIndex - diff;
|
|
3676
|
+
return fixer.removeRange([removeStartIndex, removeEndIndex]);
|
|
3677
|
+
}
|
|
3678
|
+
return null;
|
|
3679
|
+
}
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
return { list: checkListAlignment };
|
|
3684
|
+
}
|
|
3685
|
+
});
|
|
3686
|
+
|
|
3461
3687
|
//#endregion
|
|
3462
3688
|
//#region src/rules/no-laziness-blockquotes.ts
|
|
3463
|
-
/**
|
|
3464
|
-
* Helper function to get blockquote line information.
|
|
3465
|
-
*/
|
|
3466
|
-
function getBlockquoteLineInfo(line) {
|
|
3467
|
-
const regex = /^\s*(?:>\s*)*/u;
|
|
3468
|
-
const match = regex.exec(line.text);
|
|
3469
|
-
return {
|
|
3470
|
-
line,
|
|
3471
|
-
prefix: match[0],
|
|
3472
|
-
level: (match[0].match(/>/gu) || []).length
|
|
3473
|
-
};
|
|
3474
|
-
}
|
|
3475
3689
|
var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
|
|
3476
3690
|
meta: {
|
|
3477
3691
|
type: "problem",
|
|
@@ -3492,7 +3706,6 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
|
|
|
3492
3706
|
create(context) {
|
|
3493
3707
|
const sourceCode = context.sourceCode;
|
|
3494
3708
|
const checkedLines = /* @__PURE__ */ new Set();
|
|
3495
|
-
const lines = parseLines(sourceCode);
|
|
3496
3709
|
/**
|
|
3497
3710
|
* Report invalid blockquote lines.
|
|
3498
3711
|
*/
|
|
@@ -3516,15 +3729,16 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
|
|
|
3516
3729
|
for (const group of invalidGroups) {
|
|
3517
3730
|
const first = group.lines[0];
|
|
3518
3731
|
const last = group.lines.at(-1);
|
|
3732
|
+
const lines = getParsedLines(sourceCode);
|
|
3519
3733
|
context.report({
|
|
3520
3734
|
loc: {
|
|
3521
3735
|
start: {
|
|
3522
|
-
line: first.line
|
|
3736
|
+
line: first.line,
|
|
3523
3737
|
column: 1
|
|
3524
3738
|
},
|
|
3525
3739
|
end: {
|
|
3526
|
-
line: last.line
|
|
3527
|
-
column: last.line.text.length + 1
|
|
3740
|
+
line: last.line,
|
|
3741
|
+
column: lines.get(last.line).text.length + 1
|
|
3528
3742
|
}
|
|
3529
3743
|
},
|
|
3530
3744
|
messageId: "lazyBlockquoteLine",
|
|
@@ -3536,12 +3750,16 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
|
|
|
3536
3750
|
messageId: "addMarker",
|
|
3537
3751
|
data: { missingMarkers: `${base.level - group.level}` },
|
|
3538
3752
|
*fix(fixer) {
|
|
3539
|
-
for (const invalidLine of group.lines)
|
|
3753
|
+
for (const invalidLine of group.lines) {
|
|
3754
|
+
const parsedLine = lines.get(invalidLine.line);
|
|
3755
|
+
yield fixer.replaceTextRange([parsedLine.range[0], parsedLine.range[0] + invalidLine.prefix.length], base.prefix);
|
|
3756
|
+
}
|
|
3540
3757
|
}
|
|
3541
3758
|
}, {
|
|
3542
3759
|
messageId: "addLineBreak",
|
|
3543
3760
|
fix: (fixer) => {
|
|
3544
|
-
|
|
3761
|
+
const parsedLine = lines.get(first.line);
|
|
3762
|
+
return fixer.insertTextBeforeRange([parsedLine.range[0], parsedLine.range[0]], `${first.prefix.trimEnd()}\n`);
|
|
3545
3763
|
}
|
|
3546
3764
|
}]
|
|
3547
3765
|
});
|
|
@@ -3551,7 +3769,7 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
|
|
|
3551
3769
|
const loc = sourceCode.getLoc(node);
|
|
3552
3770
|
const startLine = loc.start.line;
|
|
3553
3771
|
const endLine = loc.end.line;
|
|
3554
|
-
const base =
|
|
3772
|
+
const base = getBlockquoteLevelFromLine(sourceCode, startLine);
|
|
3555
3773
|
const invalidLines = [];
|
|
3556
3774
|
for (let lineNumber = startLine + 1; lineNumber <= endLine; lineNumber++) {
|
|
3557
3775
|
if (checkedLines.has(lineNumber)) {
|
|
@@ -3560,8 +3778,7 @@ var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
|
|
|
3560
3778
|
continue;
|
|
3561
3779
|
}
|
|
3562
3780
|
checkedLines.add(lineNumber);
|
|
3563
|
-
const
|
|
3564
|
-
const current = getBlockquoteLineInfo(line);
|
|
3781
|
+
const current = getBlockquoteLevelFromLine(sourceCode, lineNumber);
|
|
3565
3782
|
if (base.level <= current.level) {
|
|
3566
3783
|
reportInvalidLines(invalidLines, base);
|
|
3567
3784
|
invalidLines.length = 0;
|
|
@@ -3638,7 +3855,7 @@ var no_multiple_empty_lines_default = createRule("no-multiple-empty-lines", {
|
|
|
3638
3855
|
toml: addIgnoreLoc,
|
|
3639
3856
|
json: addIgnoreLoc,
|
|
3640
3857
|
"root:exit"() {
|
|
3641
|
-
const lines = [...
|
|
3858
|
+
const lines = [...getParsedLines(sourceCode)];
|
|
3642
3859
|
const bofEmptyLines = [];
|
|
3643
3860
|
while (lines.length) {
|
|
3644
3861
|
if (lines[0].text.trim()) break;
|
|
@@ -3852,7 +4069,7 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
|
|
|
3852
4069
|
"root:exit"() {
|
|
3853
4070
|
const re = /[^\S\n\r]+$/u;
|
|
3854
4071
|
const skipMatch = /^[^\S\n\r]*$/u;
|
|
3855
|
-
const lines =
|
|
4072
|
+
const lines = getParsedLines(sourceCode);
|
|
3856
4073
|
const commentLineNumbers = getCommentLineNumbers();
|
|
3857
4074
|
for (const lineInfo of lines) {
|
|
3858
4075
|
const matches = re.exec(lineInfo.text);
|
|
@@ -4085,6 +4302,272 @@ var ordered_list_marker_start_default = createRule("ordered-list-marker-start",
|
|
|
4085
4302
|
}
|
|
4086
4303
|
});
|
|
4087
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
|
+
|
|
4088
4571
|
//#endregion
|
|
4089
4572
|
//#region src/rules/prefer-autolinks.ts
|
|
4090
4573
|
var prefer_autolinks_default = createRule("prefer-autolinks", {
|
|
@@ -4140,7 +4623,6 @@ var prefer_fenced_code_blocks_default = createRule("prefer-fenced-code-blocks",
|
|
|
4140
4623
|
},
|
|
4141
4624
|
create(context) {
|
|
4142
4625
|
const sourceCode = context.sourceCode;
|
|
4143
|
-
const lines = parseLines(sourceCode);
|
|
4144
4626
|
return { code(node) {
|
|
4145
4627
|
const kind = getCodeBlockKind(sourceCode, node);
|
|
4146
4628
|
if (kind === "backtick-fenced" || kind === "tilde-fenced") return;
|
|
@@ -4150,6 +4632,7 @@ var prefer_fenced_code_blocks_default = createRule("prefer-fenced-code-blocks",
|
|
|
4150
4632
|
messageId: "useFencedCodeBlock",
|
|
4151
4633
|
fix(fixer) {
|
|
4152
4634
|
if (!isFixableIndentedCodeBlock(node)) return null;
|
|
4635
|
+
const lines = getParsedLines(sourceCode);
|
|
4153
4636
|
const startColumnOffset = loc.start.column - 1;
|
|
4154
4637
|
const removeRanges = [];
|
|
4155
4638
|
let prefixText = null;
|
|
@@ -4185,6 +4668,7 @@ var prefer_fenced_code_blocks_default = createRule("prefer-fenced-code-blocks",
|
|
|
4185
4668
|
*/
|
|
4186
4669
|
function isFixableIndentedCodeBlock(node) {
|
|
4187
4670
|
if (!node.value.startsWith(" ")) return true;
|
|
4671
|
+
const lines = getParsedLines(sourceCode);
|
|
4188
4672
|
const loc = sourceCode.getLoc(node);
|
|
4189
4673
|
const firstLine = lines.get(loc.start.line);
|
|
4190
4674
|
const codeBlockFirstLine = normalizePrefix(node.value.split(/\r?\n/u)[0]);
|
|
@@ -4808,14 +5292,14 @@ var sort_definitions_default = createRule("sort-definitions", {
|
|
|
4808
5292
|
}
|
|
4809
5293
|
/** Compile order option */
|
|
4810
5294
|
function compileOption(orderOption) {
|
|
4811
|
-
const cache$
|
|
5295
|
+
const cache$2 = /* @__PURE__ */ new Map();
|
|
4812
5296
|
const compiled = compileOptionWithoutCache(orderOption);
|
|
4813
5297
|
return {
|
|
4814
5298
|
match: (node) => {
|
|
4815
|
-
const cached = cache$
|
|
5299
|
+
const cached = cache$2.get(node);
|
|
4816
5300
|
if (cached != null) return cached;
|
|
4817
5301
|
const result = compiled.match(node);
|
|
4818
|
-
cache$
|
|
5302
|
+
cache$2.set(node, result);
|
|
4819
5303
|
return result;
|
|
4820
5304
|
},
|
|
4821
5305
|
sort: compiled.sort
|
|
@@ -5057,17 +5541,20 @@ var table_header_casing_default = createRule("table-header-casing", {
|
|
|
5057
5541
|
const rules$1 = [
|
|
5058
5542
|
atx_headings_closing_sequence_length_default,
|
|
5059
5543
|
atx_headings_closing_sequence_default,
|
|
5544
|
+
blockquote_marker_alignment_default,
|
|
5060
5545
|
canonical_code_block_language_default,
|
|
5061
5546
|
definitions_last_default,
|
|
5062
5547
|
emoji_notation_default,
|
|
5063
5548
|
hard_linebreak_style_default,
|
|
5064
5549
|
heading_casing_default,
|
|
5550
|
+
list_marker_alignment_default,
|
|
5065
5551
|
no_laziness_blockquotes_default,
|
|
5066
5552
|
no_multiple_empty_lines_default,
|
|
5067
5553
|
no_text_backslash_linebreak_default,
|
|
5068
5554
|
no_trailing_spaces_default,
|
|
5069
5555
|
ordered_list_marker_sequence_default,
|
|
5070
5556
|
ordered_list_marker_start_default,
|
|
5557
|
+
padding_line_between_blocks_default,
|
|
5071
5558
|
prefer_autolinks_default,
|
|
5072
5559
|
prefer_fenced_code_blocks_default,
|
|
5073
5560
|
prefer_inline_code_words_default,
|
|
@@ -5099,9 +5586,12 @@ const plugins = {
|
|
|
5099
5586
|
}
|
|
5100
5587
|
};
|
|
5101
5588
|
const rules$2 = {
|
|
5589
|
+
"markdown-preferences/blockquote-marker-alignment": "error",
|
|
5102
5590
|
"markdown-preferences/hard-linebreak-style": "error",
|
|
5591
|
+
"markdown-preferences/list-marker-alignment": "error",
|
|
5103
5592
|
"markdown-preferences/no-laziness-blockquotes": "error",
|
|
5104
5593
|
"markdown-preferences/no-text-backslash-linebreak": "error",
|
|
5594
|
+
"markdown-preferences/padding-line-between-blocks": "error",
|
|
5105
5595
|
"markdown-preferences/prefer-autolinks": "error",
|
|
5106
5596
|
"markdown-preferences/prefer-fenced-code-blocks": "error"
|
|
5107
5597
|
};
|
|
@@ -5114,7 +5604,7 @@ __export(meta_exports, {
|
|
|
5114
5604
|
version: () => version
|
|
5115
5605
|
});
|
|
5116
5606
|
const name = "eslint-plugin-markdown-preferences";
|
|
5117
|
-
const version = "0.
|
|
5607
|
+
const version = "0.16.0";
|
|
5118
5608
|
|
|
5119
5609
|
//#endregion
|
|
5120
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",
|