eslint-markdown 0.10.0 → 0.11.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 +1 -0
- package/build/configs/all.d.ts +1 -0
- package/build/configs/stylistic.d.ts +1 -0
- package/build/rules/index.d.ts +1 -0
- package/build/rules/no-consecutive-blank-line.d.ts +60 -0
- package/package.json +1 -1
- package/src/configs/all.js +1 -0
- package/src/configs/stylistic.js +1 -0
- package/src/rules/index.js +2 -0
- package/src/rules/no-consecutive-blank-line.js +199 -0
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
[](https://www.npmjs.com/package/eslint-markdown)
|
|
9
9
|
[](https://www.npmjs.com/package/eslint-markdown)
|
|
10
|
+
[](https://www.npmjs.com/package/eslint-markdown)
|
|
10
11
|
|
|
11
12
|
> [!IMPORTANT]
|
|
12
13
|
>
|
package/build/configs/all.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export default function all(plugin: ESLint.Plugin): {
|
|
|
24
24
|
readonly 'md/consistent-strong-style': "error";
|
|
25
25
|
readonly 'md/consistent-thematic-break-style': "error";
|
|
26
26
|
readonly 'md/consistent-unordered-list-style': "error";
|
|
27
|
+
readonly 'md/no-consecutive-blank-line': "error";
|
|
27
28
|
readonly 'md/no-control-character': "error";
|
|
28
29
|
readonly 'md/no-curly-quote': "error";
|
|
29
30
|
readonly 'md/no-double-punctuation': "error";
|
|
@@ -21,6 +21,7 @@ export default function stylistic(plugin: ESLint.Plugin): {
|
|
|
21
21
|
readonly 'md/consistent-strong-style': "error";
|
|
22
22
|
readonly 'md/consistent-thematic-break-style': "error";
|
|
23
23
|
readonly 'md/consistent-unordered-list-style': "error";
|
|
24
|
+
readonly 'md/no-consecutive-blank-line': "error";
|
|
24
25
|
readonly 'md/no-tab': "error";
|
|
25
26
|
};
|
|
26
27
|
};
|
package/build/rules/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ declare const _default: {
|
|
|
9
9
|
'consistent-strong-style': import("../core/types.js").RuleModule<import("./consistent-strong-style.js").RuleOptions, "style">;
|
|
10
10
|
'consistent-thematic-break-style': import("../core/types.js").RuleModule<import("./consistent-thematic-break-style.js").RuleOptions, "style">;
|
|
11
11
|
'consistent-unordered-list-style': import("../core/types.js").RuleModule<import("./consistent-unordered-list-style.js").RuleOptions, "style">;
|
|
12
|
+
'no-consecutive-blank-line': import("../core/types.js").RuleModule<import("./no-consecutive-blank-line.js").RuleOptions, "noConsecutiveBlankLine">;
|
|
12
13
|
'no-control-character': import("../core/types.js").RuleModule<import("./no-control-character.js").RuleOptions, import("./no-control-character.js").MessageIds>;
|
|
13
14
|
'no-curly-quote': import("../core/types.js").RuleModule<import("./no-curly-quote.js").RuleOptions, "noCurlyQuote">;
|
|
14
15
|
'no-double-punctuation': import("../core/types.js").RuleModule<import("./no-double-punctuation.js").RuleOptions, import("./no-double-punctuation.js").MessageIds>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "layout";
|
|
4
|
+
docs: {
|
|
5
|
+
description: string;
|
|
6
|
+
url: string;
|
|
7
|
+
recommended: boolean;
|
|
8
|
+
stylistic: true;
|
|
9
|
+
};
|
|
10
|
+
fixable: "whitespace";
|
|
11
|
+
schema: {
|
|
12
|
+
type: "object";
|
|
13
|
+
properties: {
|
|
14
|
+
max: {
|
|
15
|
+
type: "integer";
|
|
16
|
+
minimum: number;
|
|
17
|
+
};
|
|
18
|
+
skipCode: {
|
|
19
|
+
oneOf: ({
|
|
20
|
+
type: "boolean";
|
|
21
|
+
items?: never;
|
|
22
|
+
uniqueItems?: never;
|
|
23
|
+
} | {
|
|
24
|
+
type: "array";
|
|
25
|
+
items: {
|
|
26
|
+
type: "string";
|
|
27
|
+
};
|
|
28
|
+
uniqueItems: true;
|
|
29
|
+
})[];
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
additionalProperties: false;
|
|
33
|
+
}[];
|
|
34
|
+
defaultOptions: [{
|
|
35
|
+
max: number;
|
|
36
|
+
skipCode: true;
|
|
37
|
+
}];
|
|
38
|
+
messages: {
|
|
39
|
+
noConsecutiveBlankLine: string;
|
|
40
|
+
};
|
|
41
|
+
language: string;
|
|
42
|
+
dialects: string[];
|
|
43
|
+
};
|
|
44
|
+
create(context: import("@eslint/core").RuleContext<{
|
|
45
|
+
LangOptions: import("@eslint/markdown").MarkdownLanguageOptions;
|
|
46
|
+
Code: import("@eslint/markdown").MarkdownSourceCode;
|
|
47
|
+
RuleOptions: RuleOptions;
|
|
48
|
+
Node: import("mdast").Node;
|
|
49
|
+
MessageIds: "noConsecutiveBlankLine";
|
|
50
|
+
}>): {
|
|
51
|
+
code(node: import("mdast").Code): void;
|
|
52
|
+
'root:exit'(): void;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export default _default;
|
|
56
|
+
export type RuleOptions = [{
|
|
57
|
+
max: number;
|
|
58
|
+
skipCode: boolean | string[];
|
|
59
|
+
}];
|
|
60
|
+
export type MessageIds = "noConsecutiveBlankLine";
|
package/package.json
CHANGED
package/src/configs/all.js
CHANGED
|
@@ -48,6 +48,7 @@ export default function all(plugin) {
|
|
|
48
48
|
'md/consistent-strong-style': 'error',
|
|
49
49
|
'md/consistent-thematic-break-style': 'error',
|
|
50
50
|
'md/consistent-unordered-list-style': 'error',
|
|
51
|
+
'md/no-consecutive-blank-line': 'error',
|
|
51
52
|
'md/no-control-character': 'error',
|
|
52
53
|
'md/no-curly-quote': 'error',
|
|
53
54
|
'md/no-double-punctuation': 'error',
|
package/src/configs/stylistic.js
CHANGED
|
@@ -45,6 +45,7 @@ export default function stylistic(plugin) {
|
|
|
45
45
|
'md/consistent-strong-style': 'error',
|
|
46
46
|
'md/consistent-thematic-break-style': 'error',
|
|
47
47
|
'md/consistent-unordered-list-style': 'error',
|
|
48
|
+
'md/no-consecutive-blank-line': 'error',
|
|
48
49
|
'md/no-tab': 'error',
|
|
49
50
|
},
|
|
50
51
|
});
|
package/src/rules/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import consistentThematicBreakStyle from './consistent-thematic-break-style.js';
|
|
|
15
15
|
import consistentUnorderedListStyle from './consistent-unordered-list-style.js';
|
|
16
16
|
// import enCapitalization from './en-capitalization.js';
|
|
17
17
|
// import noBoldParagraph from './no-bold-paragraph.js';
|
|
18
|
+
import noConsecutiveBlankLine from './no-consecutive-blank-line.js';
|
|
18
19
|
import noControlCharacter from './no-control-character.js';
|
|
19
20
|
import noCurlyQuote from './no-curly-quote.js';
|
|
20
21
|
import noDoublePunctuation from './no-double-punctuation.js';
|
|
@@ -43,6 +44,7 @@ export default {
|
|
|
43
44
|
'consistent-unordered-list-style': consistentUnorderedListStyle,
|
|
44
45
|
// 'en-capitalization': enCapitalization,
|
|
45
46
|
// 'no-bold-paragraph': noBoldParagraph,
|
|
47
|
+
'no-consecutive-blank-line': noConsecutiveBlankLine,
|
|
46
48
|
'no-control-character': noControlCharacter,
|
|
47
49
|
'no-curly-quote': noCurlyQuote,
|
|
48
50
|
'no-double-punctuation': noDoublePunctuation,
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule to disallow consecutive blank lines.
|
|
3
|
+
* @author lumir(lumirlumir)
|
|
4
|
+
* @see https://github.com/DavidAnson/markdownlint/blob/main/lib/md012.mjs
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// --------------------------------------------------------------------------------
|
|
8
|
+
// Import
|
|
9
|
+
// --------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
import { isBlankLine, SkipRanges } from '../core/ast/index.js';
|
|
12
|
+
import { URL_RULE_DOCS } from '../core/constants.js';
|
|
13
|
+
|
|
14
|
+
// --------------------------------------------------------------------------------
|
|
15
|
+
// Typedef
|
|
16
|
+
// --------------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @import { RuleModule } from '../core/types.js';
|
|
20
|
+
* @typedef {[{ max: number, skipCode: boolean | string[] }]} RuleOptions
|
|
21
|
+
* @typedef {'noConsecutiveBlankLine'} MessageIds
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// --------------------------------------------------------------------------------
|
|
25
|
+
// Rule Definition
|
|
26
|
+
// --------------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/** @type {RuleModule<RuleOptions, MessageIds>} */
|
|
29
|
+
export default {
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'layout',
|
|
32
|
+
|
|
33
|
+
docs: {
|
|
34
|
+
description: 'Disallow consecutive blank lines',
|
|
35
|
+
url: URL_RULE_DOCS('no-consecutive-blank-line'),
|
|
36
|
+
recommended: false,
|
|
37
|
+
stylistic: true,
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
fixable: 'whitespace',
|
|
41
|
+
|
|
42
|
+
schema: [
|
|
43
|
+
{
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
max: {
|
|
47
|
+
type: 'integer',
|
|
48
|
+
minimum: 1,
|
|
49
|
+
},
|
|
50
|
+
skipCode: {
|
|
51
|
+
oneOf: [
|
|
52
|
+
{
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
},
|
|
60
|
+
uniqueItems: true,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
additionalProperties: false,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
|
|
69
|
+
defaultOptions: [
|
|
70
|
+
{
|
|
71
|
+
max: 1,
|
|
72
|
+
skipCode: true,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
|
|
76
|
+
messages: {
|
|
77
|
+
noConsecutiveBlankLine:
|
|
78
|
+
'More than {{ max }} consecutive blank line(s) are not allowed.',
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
language: 'markdown',
|
|
82
|
+
|
|
83
|
+
dialects: ['commonmark', 'gfm'],
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
create(context) {
|
|
87
|
+
const { options, sourceCode } = context;
|
|
88
|
+
const [{ max, skipCode }] = options;
|
|
89
|
+
const { lines, text } = sourceCode;
|
|
90
|
+
|
|
91
|
+
const skipRanges = new SkipRanges();
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
code(node) {
|
|
95
|
+
if (
|
|
96
|
+
Array.isArray(skipCode) ? node.lang && skipCode.includes(node.lang) : skipCode
|
|
97
|
+
)
|
|
98
|
+
skipRanges.push(sourceCode.getRange(node)); // Store range information of `Code`.
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
'root:exit'() {
|
|
102
|
+
let consecutiveBlankLineCount = 0;
|
|
103
|
+
|
|
104
|
+
for (let currentLineIdx = 0; currentLineIdx < lines.length; currentLineIdx++) {
|
|
105
|
+
const startLoc = /** @type {const} */ ({
|
|
106
|
+
line: currentLineIdx + 1,
|
|
107
|
+
column: 1,
|
|
108
|
+
});
|
|
109
|
+
const endLoc = /** @type {const} */ ({
|
|
110
|
+
line: currentLineIdx + 2,
|
|
111
|
+
column: 1,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
skipRanges.includes(sourceCode.getIndexFromLoc(startLoc)) ||
|
|
116
|
+
!isBlankLine(lines[currentLineIdx])
|
|
117
|
+
) {
|
|
118
|
+
consecutiveBlankLineCount = 0;
|
|
119
|
+
} else {
|
|
120
|
+
consecutiveBlankLineCount++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (max < consecutiveBlankLineCount) {
|
|
124
|
+
const lastAllowedBlankLineIdx =
|
|
125
|
+
currentLineIdx - consecutiveBlankLineCount + max;
|
|
126
|
+
|
|
127
|
+
context.report({
|
|
128
|
+
loc: {
|
|
129
|
+
start: startLoc,
|
|
130
|
+
end: endLoc,
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
data: {
|
|
134
|
+
max,
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
messageId: 'noConsecutiveBlankLine',
|
|
138
|
+
|
|
139
|
+
fix(fixer) {
|
|
140
|
+
/*
|
|
141
|
+
* When the consecutive blank-line run reaches EOF, the fixer must remove the
|
|
142
|
+
* whole excess tail at once instead of removing only the current line.
|
|
143
|
+
*
|
|
144
|
+
* `currentLineIdx + 1 === lines.length` means the current line is the final
|
|
145
|
+
* logical line in `sourceCode.lines`. In a case like `foo\n\n\n ` with
|
|
146
|
+
* `max: 1`, the final line is still a blank line, but it may contain spaces.
|
|
147
|
+
* If we only remove from the current line start, those trailing spaces can
|
|
148
|
+
* survive and produce `foo\n ` instead of the intended `foo\n`.
|
|
149
|
+
*
|
|
150
|
+
* The start of the removal range is calculated from the last blank line that
|
|
151
|
+
* is still allowed by `max`:
|
|
152
|
+
*
|
|
153
|
+
* currentLineIdx - consecutiveBlankLineCount
|
|
154
|
+
* -> zero-based index of the first line in the current blank-line run
|
|
155
|
+
*
|
|
156
|
+
* + max
|
|
157
|
+
* -> zero-based index of the last blank line we want to keep
|
|
158
|
+
*
|
|
159
|
+
* + 1
|
|
160
|
+
* -> convert the zero-based line index to ESLint's one-based loc line
|
|
161
|
+
*
|
|
162
|
+
* The column is `lines[lastAllowedBlankLineIdx].length + 1`, which points to the
|
|
163
|
+
* end of that allowed blank line. The range then ends at `text.length`, so
|
|
164
|
+
* everything after the allowed blank line is removed, including remaining blank
|
|
165
|
+
* lines, line endings, and any spaces on the final blank line.
|
|
166
|
+
*
|
|
167
|
+
* Example with `foo\n\n\n ` and `max: 1`:
|
|
168
|
+
*
|
|
169
|
+
* lines = ['foo', '', '', ' ']
|
|
170
|
+
* currentLineIdx = 3
|
|
171
|
+
* consecutiveBlankLineCount = 3
|
|
172
|
+
* max = 1
|
|
173
|
+
*
|
|
174
|
+
* lastAllowedBlankLineIdx = 3 - 3 + 1 = 1
|
|
175
|
+
*
|
|
176
|
+
* So the fixer removes from the end of line 2 to EOF, producing `foo\n`.
|
|
177
|
+
*/
|
|
178
|
+
if (currentLineIdx + 1 === lines.length) {
|
|
179
|
+
return fixer.removeRange([
|
|
180
|
+
sourceCode.getIndexFromLoc({
|
|
181
|
+
line: lastAllowedBlankLineIdx + 1,
|
|
182
|
+
column: lines[lastAllowedBlankLineIdx].length + 1,
|
|
183
|
+
}),
|
|
184
|
+
text.length,
|
|
185
|
+
]);
|
|
186
|
+
} else {
|
|
187
|
+
return fixer.removeRange([
|
|
188
|
+
sourceCode.getIndexFromLoc(startLoc),
|
|
189
|
+
sourceCode.getIndexFromLoc(endLoc),
|
|
190
|
+
]);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
};
|