markdownlint 0.29.0 → 0.30.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/CHANGELOG.md +7 -0
- package/demo/markdownlint-browser.js +713 -692
- package/doc/CustomRules.md +2 -0
- package/doc/Rules.md +33 -17
- package/doc/md022.md +12 -6
- package/doc/md026.md +1 -2
- package/doc/md049.md +2 -2
- package/doc/md050.md +2 -2
- package/doc/md051.md +17 -6
- package/helpers/helpers.js +83 -230
- package/helpers/micromark.cjs +145 -34
- package/helpers/package.json +2 -2
- package/helpers/shared.js +7 -0
- package/lib/cache.js +0 -2
- package/lib/constants.js +1 -1
- package/lib/markdownlint.d.ts +4 -0
- package/lib/markdownlint.js +21 -7
- package/lib/md022.js +80 -40
- package/lib/md026.js +19 -12
- package/lib/md032.js +57 -31
- package/lib/md033.js +19 -45
- package/lib/md037.js +74 -161
- package/lib/md045.js +17 -5
- package/lib/md051.js +90 -68
- package/package.json +23 -22
- package/schema/.markdownlint.jsonc +3 -3
- package/schema/.markdownlint.yaml +3 -3
- package/schema/markdownlint-config-schema.json +19 -7
package/helpers/micromark.cjs
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
const {
|
|
7
|
-
gfmAutolinkLiteral, gfmFootnote, gfmTable, parse, postprocess,
|
|
7
|
+
gfmAutolinkLiteral, gfmFootnote, gfmTable, math, parse, postprocess,
|
|
8
|
+
preprocess
|
|
8
9
|
// @ts-ignore
|
|
9
10
|
} = require("markdownlint-micromark");
|
|
11
|
+
const { newLineRe } = require("./shared.js");
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Markdown token.
|
|
@@ -19,27 +21,37 @@ const {
|
|
|
19
21
|
* @property {number} endColumn End column (1-based).
|
|
20
22
|
* @property {string} text Token text.
|
|
21
23
|
* @property {Token[]} children Child tokens.
|
|
24
|
+
* @property {Token[]} [htmlFlowChildren] Child tokens for htmlFlow.
|
|
22
25
|
*/
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* Parses a Markdown document and returns Micromark events.
|
|
26
29
|
*
|
|
27
30
|
* @param {string} markdown Markdown document.
|
|
28
|
-
* @param {Object} [
|
|
29
|
-
* @param {boolean} [
|
|
31
|
+
* @param {Object} [micromarkOptions] Options for micromark.
|
|
32
|
+
* @param {boolean} [referencesDefined] Treat references as defined.
|
|
30
33
|
* @returns {Object[]} Micromark events.
|
|
31
34
|
*/
|
|
32
|
-
function getMicromarkEvents(
|
|
35
|
+
function getMicromarkEvents(
|
|
36
|
+
markdown,
|
|
37
|
+
micromarkOptions = {},
|
|
38
|
+
referencesDefined = true
|
|
39
|
+
) {
|
|
33
40
|
|
|
34
41
|
// Customize options object to add useful extensions
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
micromarkOptions.extensions = micromarkOptions.extensions || [];
|
|
43
|
+
micromarkOptions.extensions.push(
|
|
44
|
+
gfmAutolinkLiteral(),
|
|
45
|
+
gfmFootnote(),
|
|
46
|
+
gfmTable(),
|
|
47
|
+
math()
|
|
48
|
+
);
|
|
37
49
|
|
|
38
50
|
// Use micromark to parse document into Events
|
|
39
51
|
const encoding = undefined;
|
|
40
52
|
const eol = true;
|
|
41
|
-
const parseContext = parse(
|
|
42
|
-
if (
|
|
53
|
+
const parseContext = parse(micromarkOptions);
|
|
54
|
+
if (referencesDefined) {
|
|
43
55
|
// Customize ParseContext to treat all references as defined
|
|
44
56
|
parseContext.defined.includes = (searchElement) => searchElement.length > 0;
|
|
45
57
|
}
|
|
@@ -52,15 +64,21 @@ function getMicromarkEvents(markdown, options = {}, refsDefined = true) {
|
|
|
52
64
|
* Parses a Markdown document and returns (frozen) tokens.
|
|
53
65
|
*
|
|
54
66
|
* @param {string} markdown Markdown document.
|
|
55
|
-
* @param {Object}
|
|
56
|
-
* @param {boolean}
|
|
67
|
+
* @param {Object} micromarkOptions Options for micromark.
|
|
68
|
+
* @param {boolean} referencesDefined Treat references as defined.
|
|
69
|
+
* @param {number} lineDelta Offset to apply to start/end line.
|
|
57
70
|
* @returns {Token[]} Micromark tokens (frozen).
|
|
58
71
|
*/
|
|
59
|
-
function
|
|
60
|
-
|
|
72
|
+
function micromarkParseWithOffset(
|
|
73
|
+
markdown,
|
|
74
|
+
micromarkOptions,
|
|
75
|
+
referencesDefined,
|
|
76
|
+
lineDelta
|
|
77
|
+
) {
|
|
61
78
|
// Use micromark to parse document into Events
|
|
62
|
-
const events =
|
|
63
|
-
|
|
79
|
+
const events = getMicromarkEvents(
|
|
80
|
+
markdown, micromarkOptions, referencesDefined
|
|
81
|
+
);
|
|
64
82
|
|
|
65
83
|
// Create Token objects
|
|
66
84
|
const document = [];
|
|
@@ -68,6 +86,8 @@ function micromarkParse(markdown, options = {}, refsDefined = true) {
|
|
|
68
86
|
"children": document
|
|
69
87
|
};
|
|
70
88
|
const history = [ current ];
|
|
89
|
+
let reparseOptions = null;
|
|
90
|
+
let lines = null;
|
|
71
91
|
for (const event of events) {
|
|
72
92
|
const [ kind, token, context ] = event;
|
|
73
93
|
const { type, start, end } = token;
|
|
@@ -79,13 +99,37 @@ function micromarkParse(markdown, options = {}, refsDefined = true) {
|
|
|
79
99
|
history.push(previous);
|
|
80
100
|
current = {
|
|
81
101
|
type,
|
|
82
|
-
startLine,
|
|
102
|
+
"startLine": startLine + lineDelta,
|
|
83
103
|
startColumn,
|
|
84
|
-
endLine,
|
|
104
|
+
"endLine": endLine + lineDelta,
|
|
85
105
|
endColumn,
|
|
86
106
|
text,
|
|
87
107
|
"children": []
|
|
88
108
|
};
|
|
109
|
+
if (current.type === "htmlFlow") {
|
|
110
|
+
if (!reparseOptions || !lines) {
|
|
111
|
+
reparseOptions = {
|
|
112
|
+
...micromarkOptions,
|
|
113
|
+
"extensions": [
|
|
114
|
+
{
|
|
115
|
+
"disable": {
|
|
116
|
+
"null": [ "codeIndented", "htmlFlow" ]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
lines = markdown.split(newLineRe);
|
|
122
|
+
}
|
|
123
|
+
const reparseMarkdown = lines
|
|
124
|
+
.slice(current.startLine - 1, current.endLine)
|
|
125
|
+
.join("\n");
|
|
126
|
+
current.htmlFlowChildren = micromarkParseWithOffset(
|
|
127
|
+
reparseMarkdown,
|
|
128
|
+
reparseOptions,
|
|
129
|
+
referencesDefined,
|
|
130
|
+
current.startLine - 1
|
|
131
|
+
);
|
|
132
|
+
}
|
|
89
133
|
previous.children.push(current);
|
|
90
134
|
} else if (kind === "exit") {
|
|
91
135
|
Object.freeze(current.children);
|
|
@@ -100,24 +144,26 @@ function micromarkParse(markdown, options = {}, refsDefined = true) {
|
|
|
100
144
|
return document;
|
|
101
145
|
}
|
|
102
146
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Parses a Markdown document and returns (frozen) tokens.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} markdown Markdown document.
|
|
151
|
+
* @param {Object} [micromarkOptions] Options for micromark.
|
|
152
|
+
* @param {boolean} [referencesDefined] Treat references as defined.
|
|
153
|
+
* @returns {Token[]} Micromark tokens (frozen).
|
|
154
|
+
*/
|
|
155
|
+
function micromarkParse(
|
|
156
|
+
markdown,
|
|
157
|
+
micromarkOptions = {},
|
|
158
|
+
referencesDefined = true
|
|
159
|
+
) {
|
|
160
|
+
return micromarkParseWithOffset(
|
|
161
|
+
markdown,
|
|
162
|
+
micromarkOptions,
|
|
163
|
+
referencesDefined,
|
|
164
|
+
0
|
|
165
|
+
);
|
|
166
|
+
}
|
|
121
167
|
|
|
122
168
|
/**
|
|
123
169
|
* Filter a list of Micromark tokens by predicate.
|
|
@@ -158,6 +204,68 @@ function filterByTypes(tokens, allowed) {
|
|
|
158
204
|
);
|
|
159
205
|
}
|
|
160
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Filter a list of Micromark tokens for HTML tokens.
|
|
209
|
+
*
|
|
210
|
+
* @param {Token[]} tokens Micromark tokens.
|
|
211
|
+
* @returns {Token[]} Filtered tokens.
|
|
212
|
+
*/
|
|
213
|
+
function filterByHtmlTokens(tokens) {
|
|
214
|
+
const result = [];
|
|
215
|
+
const pending = [ tokens ];
|
|
216
|
+
let current = null;
|
|
217
|
+
while ((current = pending.shift())) {
|
|
218
|
+
for (const token of filterByTypes(current, [ "htmlFlow", "htmlText" ])) {
|
|
219
|
+
if (token.type === "htmlText") {
|
|
220
|
+
result.push(token);
|
|
221
|
+
} else {
|
|
222
|
+
// token.type === "htmlFlow"
|
|
223
|
+
// @ts-ignore
|
|
224
|
+
pending.push(token.htmlFlowChildren);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Returns a list of all nested child tokens.
|
|
233
|
+
*
|
|
234
|
+
* @param {Token} parent Micromark token.
|
|
235
|
+
* @returns {Token[]} Flattened children.
|
|
236
|
+
*/
|
|
237
|
+
function flattenedChildren(parent) {
|
|
238
|
+
const result = [];
|
|
239
|
+
const pending = [ ...parent.children ];
|
|
240
|
+
let token = null;
|
|
241
|
+
while ((token = pending.shift())) {
|
|
242
|
+
result.push(token);
|
|
243
|
+
pending.unshift(...token.children);
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Gets the heading level of a Micromark heading tokan.
|
|
250
|
+
*
|
|
251
|
+
* @param {Token} heading Micromark heading token.
|
|
252
|
+
* @returns {number} Heading level.
|
|
253
|
+
*/
|
|
254
|
+
function getHeadingLevel(heading) {
|
|
255
|
+
const headingSequence = filterByTypes(
|
|
256
|
+
heading.children,
|
|
257
|
+
[ "atxHeadingSequence", "setextHeadingLineSequence" ]
|
|
258
|
+
);
|
|
259
|
+
let level = 1;
|
|
260
|
+
const { text } = headingSequence[0];
|
|
261
|
+
if (text[0] === "#") {
|
|
262
|
+
level = Math.min(text.length, 6);
|
|
263
|
+
} else if (text[0] === "-") {
|
|
264
|
+
level = 2;
|
|
265
|
+
}
|
|
266
|
+
return level;
|
|
267
|
+
}
|
|
268
|
+
|
|
161
269
|
/**
|
|
162
270
|
* Gets information about the tag in an HTML token.
|
|
163
271
|
*
|
|
@@ -230,8 +338,11 @@ function tokenIfType(token, type) {
|
|
|
230
338
|
|
|
231
339
|
module.exports = {
|
|
232
340
|
"parse": micromarkParse,
|
|
341
|
+
filterByHtmlTokens,
|
|
233
342
|
filterByPredicate,
|
|
234
343
|
filterByTypes,
|
|
344
|
+
flattenedChildren,
|
|
345
|
+
getHeadingLevel,
|
|
235
346
|
getHtmlTagInfo,
|
|
236
347
|
getMicromarkEvents,
|
|
237
348
|
getTokenTextByType,
|
package/helpers/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markdownlint-rule-helpers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "A collection of markdownlint helper functions for custom rules",
|
|
5
5
|
"main": "./helpers.js",
|
|
6
6
|
"exports": "./helpers.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"bugs": "https://github.com/DavidAnson/markdownlint/issues",
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
16
|
+
"node": ">=16"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"markdownlint-micromark": "0.1.2"
|
package/lib/cache.js
CHANGED
|
@@ -15,8 +15,6 @@ module.exports.codeBlockAndSpanRanges =
|
|
|
15
15
|
() => map.get("codeBlockAndSpanRanges");
|
|
16
16
|
module.exports.flattenedLists =
|
|
17
17
|
() => map.get("flattenedLists");
|
|
18
|
-
module.exports.htmlElementRanges =
|
|
19
|
-
() => map.get("htmlElementRanges");
|
|
20
18
|
module.exports.lineMetadata =
|
|
21
19
|
() => map.get("lineMetadata");
|
|
22
20
|
module.exports.referenceLinkImageData =
|
package/lib/constants.js
CHANGED
package/lib/markdownlint.d.ts
CHANGED
package/lib/markdownlint.js
CHANGED
|
@@ -59,7 +59,7 @@ function validateRuleList(ruleList, synchronous) {
|
|
|
59
59
|
if (
|
|
60
60
|
!result &&
|
|
61
61
|
rule.information &&
|
|
62
|
-
|
|
62
|
+
!helpers.isUrl(rule.information)
|
|
63
63
|
) {
|
|
64
64
|
result = newError("information");
|
|
65
65
|
}
|
|
@@ -481,7 +481,7 @@ function getEnabledRulesPerLineNumber(
|
|
|
481
481
|
applyEnableDisable(
|
|
482
482
|
action,
|
|
483
483
|
parameter,
|
|
484
|
-
enabledRulesPerLineNumber[nextLineNumber]
|
|
484
|
+
enabledRulesPerLineNumber[nextLineNumber]
|
|
485
485
|
);
|
|
486
486
|
}
|
|
487
487
|
}
|
|
@@ -583,14 +583,11 @@ function lintContent(
|
|
|
583
583
|
helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata);
|
|
584
584
|
const flattenedLists =
|
|
585
585
|
helpers.flattenLists(paramsBase.parsers.markdownit.tokens);
|
|
586
|
-
const htmlElementRanges =
|
|
587
|
-
helpers.htmlElementRanges(paramsBase, lineMetadata);
|
|
588
586
|
const referenceLinkImageData =
|
|
589
587
|
helpers.getReferenceLinkImageData(paramsBase);
|
|
590
588
|
cache.set({
|
|
591
589
|
codeBlockAndSpanRanges,
|
|
592
590
|
flattenedLists,
|
|
593
|
-
htmlElementRanges,
|
|
594
591
|
lineMetadata,
|
|
595
592
|
referenceLinkImageData
|
|
596
593
|
});
|
|
@@ -629,6 +626,10 @@ function lintContent(
|
|
|
629
626
|
!helpers.isString(errorInfo.context)) {
|
|
630
627
|
throwError("context");
|
|
631
628
|
}
|
|
629
|
+
if (errorInfo.information &&
|
|
630
|
+
!helpers.isUrl(errorInfo.information)) {
|
|
631
|
+
throwError("information");
|
|
632
|
+
}
|
|
632
633
|
if (errorInfo.range &&
|
|
633
634
|
(!Array.isArray(errorInfo.range) ||
|
|
634
635
|
(errorInfo.range.length !== 2) ||
|
|
@@ -681,12 +682,13 @@ function lintContent(
|
|
|
681
682
|
cleanFixInfo.insertText = fixInfo.insertText;
|
|
682
683
|
}
|
|
683
684
|
}
|
|
685
|
+
const information = errorInfo.information || rule.information;
|
|
684
686
|
results.push({
|
|
685
687
|
lineNumber,
|
|
686
688
|
"ruleName": rule.names[0],
|
|
687
689
|
"ruleNames": rule.names,
|
|
688
690
|
"ruleDescription": rule.description,
|
|
689
|
-
"ruleInformation":
|
|
691
|
+
"ruleInformation": information ? information.href : null,
|
|
690
692
|
"errorDetail": errorInfo.detail || null,
|
|
691
693
|
"errorContext": errorInfo.context || null,
|
|
692
694
|
"errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
|
|
@@ -864,8 +866,19 @@ function lintInput(options, synchronous, callback) {
|
|
|
864
866
|
// Normalize inputs
|
|
865
867
|
options = options || {};
|
|
866
868
|
callback = callback || function noop() {};
|
|
869
|
+
const customRuleList =
|
|
870
|
+
[ options.customRules || [] ]
|
|
871
|
+
.flat()
|
|
872
|
+
.map((rule) => ({
|
|
873
|
+
"names": helpers.cloneIfArray(rule.names),
|
|
874
|
+
"description": rule.description,
|
|
875
|
+
"information": helpers.cloneIfUrl(rule.information),
|
|
876
|
+
"tags": helpers.cloneIfArray(rule.tags),
|
|
877
|
+
"asynchronous": rule.asynchronous,
|
|
878
|
+
"function": rule.function
|
|
879
|
+
}));
|
|
867
880
|
// eslint-disable-next-line unicorn/prefer-spread
|
|
868
|
-
const ruleList = rules.concat(
|
|
881
|
+
const ruleList = rules.concat(customRuleList);
|
|
869
882
|
const ruleErr = validateRuleList(ruleList, synchronous);
|
|
870
883
|
if (ruleErr) {
|
|
871
884
|
callback(ruleErr);
|
|
@@ -1314,6 +1327,7 @@ module.exports = markdownlint;
|
|
|
1314
1327
|
* @property {number} lineNumber Line number (1-based).
|
|
1315
1328
|
* @property {string} [detail] Detail about the error.
|
|
1316
1329
|
* @property {string} [context] Context for the error.
|
|
1330
|
+
* @property {URL} [information] Link to more information.
|
|
1317
1331
|
* @property {number[]} [range] Column number (1-based) and length.
|
|
1318
1332
|
* @property {RuleOnErrorFixInfo} [fixInfo] Fix information.
|
|
1319
1333
|
*/
|
package/lib/md022.js
CHANGED
|
@@ -2,8 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const { addErrorDetailIf, blockquotePrefixRe,
|
|
5
|
+
const { addErrorDetailIf, blockquotePrefixRe, isBlankLine } =
|
|
6
6
|
require("../helpers");
|
|
7
|
+
const { filterByTypes, getHeadingLevel } =
|
|
8
|
+
require("../helpers/micromark.cjs");
|
|
9
|
+
|
|
10
|
+
const defaultLines = 1;
|
|
11
|
+
|
|
12
|
+
const getLinesFunction = (linesParam) => {
|
|
13
|
+
if (Array.isArray(linesParam)) {
|
|
14
|
+
const linesArray = new Array(6).fill(defaultLines);
|
|
15
|
+
for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) {
|
|
16
|
+
linesArray[index] = value;
|
|
17
|
+
}
|
|
18
|
+
return (heading) => linesArray[getHeadingLevel(heading) - 1];
|
|
19
|
+
}
|
|
20
|
+
// Coerce linesParam to a number
|
|
21
|
+
const lines = (linesParam === undefined) ? defaultLines : Number(linesParam);
|
|
22
|
+
return () => lines;
|
|
23
|
+
};
|
|
7
24
|
|
|
8
25
|
const getBlockQuote = (str, count) => (
|
|
9
26
|
(str || "")
|
|
@@ -19,50 +36,73 @@ module.exports = {
|
|
|
19
36
|
"description": "Headings should be surrounded by blank lines",
|
|
20
37
|
"tags": [ "headings", "headers", "blank_lines" ],
|
|
21
38
|
"function": function MD022(params, onError) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
const getLinesAbove = getLinesFunction(params.config.lines_above);
|
|
40
|
+
const getLinesBelow = getLinesFunction(params.config.lines_below);
|
|
41
|
+
const { lines, parsers } = params;
|
|
42
|
+
const headings = filterByTypes(
|
|
43
|
+
parsers.micromark.tokens,
|
|
44
|
+
[ "atxHeading", "setextHeading" ]
|
|
45
|
+
);
|
|
46
|
+
for (const heading of headings) {
|
|
47
|
+
const { startLine, endLine } = heading;
|
|
48
|
+
const line = lines[startLine - 1].trim();
|
|
49
|
+
|
|
50
|
+
// Check lines above
|
|
51
|
+
const linesAbove = getLinesAbove(heading);
|
|
52
|
+
if (linesAbove >= 0) {
|
|
53
|
+
let actualAbove = 0;
|
|
54
|
+
for (
|
|
55
|
+
let i = 0;
|
|
56
|
+
(i < linesAbove) && isBlankLine(lines[startLine - 2 - i]);
|
|
57
|
+
i++
|
|
58
|
+
) {
|
|
32
59
|
actualAbove++;
|
|
33
60
|
}
|
|
61
|
+
addErrorDetailIf(
|
|
62
|
+
onError,
|
|
63
|
+
startLine,
|
|
64
|
+
linesAbove,
|
|
65
|
+
actualAbove,
|
|
66
|
+
"Above",
|
|
67
|
+
line,
|
|
68
|
+
null,
|
|
69
|
+
{
|
|
70
|
+
"insertText": getBlockQuote(
|
|
71
|
+
lines[startLine - 2],
|
|
72
|
+
linesAbove - actualAbove
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
);
|
|
34
76
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
getBlockQuote(lines[topIndex - 1], linesAbove - actualAbove)
|
|
46
|
-
});
|
|
47
|
-
let actualBelow = 0;
|
|
48
|
-
for (let i = 0; i < linesBelow; i++) {
|
|
49
|
-
if (isBlankLine(lines[nextIndex + i])) {
|
|
77
|
+
|
|
78
|
+
// Check lines below
|
|
79
|
+
const linesBelow = getLinesBelow(heading);
|
|
80
|
+
if (linesBelow >= 0) {
|
|
81
|
+
let actualBelow = 0;
|
|
82
|
+
for (
|
|
83
|
+
let i = 0;
|
|
84
|
+
(i < linesBelow) && isBlankLine(lines[endLine + i]);
|
|
85
|
+
i++
|
|
86
|
+
) {
|
|
50
87
|
actualBelow++;
|
|
51
88
|
}
|
|
89
|
+
addErrorDetailIf(
|
|
90
|
+
onError,
|
|
91
|
+
startLine,
|
|
92
|
+
linesBelow,
|
|
93
|
+
actualBelow,
|
|
94
|
+
"Below",
|
|
95
|
+
line,
|
|
96
|
+
null,
|
|
97
|
+
{
|
|
98
|
+
"lineNumber": endLine + 1,
|
|
99
|
+
"insertText": getBlockQuote(
|
|
100
|
+
lines[endLine],
|
|
101
|
+
linesBelow - actualBelow
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
);
|
|
52
105
|
}
|
|
53
|
-
|
|
54
|
-
onError,
|
|
55
|
-
topIndex + 1,
|
|
56
|
-
linesBelow,
|
|
57
|
-
actualBelow,
|
|
58
|
-
"Below",
|
|
59
|
-
lines[topIndex].trim(),
|
|
60
|
-
null,
|
|
61
|
-
{
|
|
62
|
-
"lineNumber": nextIndex + 1,
|
|
63
|
-
"insertText":
|
|
64
|
-
getBlockQuote(lines[nextIndex], linesBelow - actualBelow)
|
|
65
|
-
});
|
|
66
|
-
});
|
|
106
|
+
}
|
|
67
107
|
}
|
|
68
108
|
};
|
package/lib/md026.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const { addError, allPunctuationNoQuestion,
|
|
6
|
-
require("../helpers");
|
|
5
|
+
const { addError, allPunctuationNoQuestion, endOfLineGemojiCodeRe,
|
|
6
|
+
endOfLineHtmlEntityRe, escapeForRegExp } = require("../helpers");
|
|
7
|
+
const { filterByTypes } = require("../helpers/micromark.cjs");
|
|
7
8
|
|
|
8
|
-
const endOfLineHtmlEntityRe = /&#?[\da-zA-Z]+;$/;
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
"names": [ "MD026", "no-trailing-punctuation" ],
|
|
@@ -18,19 +18,26 @@ module.exports = {
|
|
|
18
18
|
);
|
|
19
19
|
const trailingPunctuationRe =
|
|
20
20
|
new RegExp("\\s*[" + escapeForRegExp(punctuation) + "]+$");
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const headings = filterByTypes(
|
|
22
|
+
params.parsers.micromark.tokens,
|
|
23
|
+
[ "atxHeadingText", "setextHeadingText" ]
|
|
24
|
+
);
|
|
25
|
+
for (const heading of headings) {
|
|
26
|
+
const { endLine, startColumn, text } = heading;
|
|
27
|
+
const match = trailingPunctuationRe.exec(text);
|
|
28
|
+
if (
|
|
29
|
+
match &&
|
|
30
|
+
!endOfLineHtmlEntityRe.test(text) &&
|
|
31
|
+
!endOfLineGemojiCodeRe.test(text)
|
|
32
|
+
) {
|
|
26
33
|
const fullMatch = match[0];
|
|
27
|
-
const column = match.index
|
|
34
|
+
const column = startColumn + match.index;
|
|
28
35
|
const length = fullMatch.length;
|
|
29
36
|
addError(
|
|
30
37
|
onError,
|
|
31
|
-
|
|
38
|
+
endLine,
|
|
32
39
|
`Punctuation: '${fullMatch}'`,
|
|
33
|
-
|
|
40
|
+
undefined,
|
|
34
41
|
[ column, length ],
|
|
35
42
|
{
|
|
36
43
|
"editColumn": column,
|
|
@@ -38,6 +45,6 @@ module.exports = {
|
|
|
38
45
|
}
|
|
39
46
|
);
|
|
40
47
|
}
|
|
41
|
-
}
|
|
48
|
+
}
|
|
42
49
|
}
|
|
43
50
|
};
|