markdownlint 0.22.0 → 0.25.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/CONTRIBUTING.md +13 -3
- package/LICENSE +1 -1
- package/README.md +140 -83
- package/demo/markdownlint-browser.js +1599 -3309
- package/doc/CustomRules.md +15 -4
- package/doc/Prettier.md +23 -0
- package/doc/Rules.md +191 -80
- package/helpers/LICENSE +1 -1
- package/helpers/helpers.js +260 -68
- package/helpers/package.json +1 -1
- package/lib/cache.js +14 -5
- package/lib/constants.js +7 -0
- package/lib/markdownlint.d.ts +31 -11
- package/lib/markdownlint.js +336 -254
- package/lib/md002.js +1 -1
- package/lib/md004.js +46 -13
- package/lib/md006.js +1 -1
- package/lib/md007.js +4 -2
- package/lib/md009.js +24 -20
- package/lib/md010.js +25 -16
- package/lib/md011.js +31 -27
- package/lib/md018.js +1 -1
- package/lib/md019.js +1 -1
- package/lib/md020.js +1 -1
- package/lib/md021.js +1 -1
- package/lib/md023.js +1 -1
- package/lib/md025.js +1 -1
- package/lib/md032.js +2 -2
- package/lib/md033.js +12 -8
- package/lib/md035.js +4 -4
- package/lib/md037.js +40 -12
- package/lib/md039.js +4 -3
- package/lib/md041.js +14 -3
- package/lib/md043.js +9 -5
- package/lib/md044.js +54 -71
- package/lib/md049.js +45 -0
- package/lib/md050.js +45 -0
- package/lib/rules.js +6 -5
- package/package.json +46 -37
- package/schema/.markdownlint.jsonc +266 -0
- package/schema/.markdownlint.yaml +239 -0
- package/schema/markdownlint-config-schema.json +129 -22
- package/schema/build-config-schema.js +0 -417
package/lib/md002.js
CHANGED
|
@@ -6,7 +6,7 @@ const { addErrorDetailIf } = require("../helpers");
|
|
|
6
6
|
|
|
7
7
|
module.exports = {
|
|
8
8
|
"names": [ "MD002", "first-heading-h1", "first-header-h1" ],
|
|
9
|
-
"description": "First heading should be a top
|
|
9
|
+
"description": "First heading should be a top-level heading",
|
|
10
10
|
"tags": [ "headings", "headers" ],
|
|
11
11
|
"function": function MD002(params, onError) {
|
|
12
12
|
const level = Number(params.config.level || 1);
|
package/lib/md004.js
CHANGED
|
@@ -2,10 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const { addErrorDetailIf, listItemMarkerRe,
|
|
6
|
-
|
|
5
|
+
const { addErrorDetailIf, listItemMarkerRe, unorderedListStyleFor } =
|
|
6
|
+
require("../helpers");
|
|
7
7
|
const { flattenedLists } = require("./cache");
|
|
8
8
|
|
|
9
|
+
const expectedStyleToMarker = {
|
|
10
|
+
"dash": "-",
|
|
11
|
+
"plus": "+",
|
|
12
|
+
"asterisk": "*"
|
|
13
|
+
};
|
|
14
|
+
const differentItemStyle = {
|
|
15
|
+
"dash": "plus",
|
|
16
|
+
"plus": "asterisk",
|
|
17
|
+
"asterisk": "dash"
|
|
18
|
+
};
|
|
19
|
+
const validStyles = Object.keys(expectedStyleToMarker);
|
|
20
|
+
|
|
9
21
|
module.exports = {
|
|
10
22
|
"names": [ "MD004", "ul-style" ],
|
|
11
23
|
"description": "Unordered list style",
|
|
@@ -23,19 +35,40 @@ module.exports = {
|
|
|
23
35
|
const itemStyle = unorderedListStyleFor(item);
|
|
24
36
|
if (style === "sublist") {
|
|
25
37
|
const nesting = list.nesting;
|
|
26
|
-
if (!nestingStyles[nesting]
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
nestingStyles[nesting], itemStyle, null, null,
|
|
32
|
-
rangeFromRegExp(item.line, listItemMarkerRe));
|
|
38
|
+
if (!nestingStyles[nesting]) {
|
|
39
|
+
nestingStyles[nesting] =
|
|
40
|
+
(itemStyle === nestingStyles[nesting - 1]) ?
|
|
41
|
+
differentItemStyle[itemStyle] :
|
|
42
|
+
itemStyle;
|
|
33
43
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
expectedStyle = nestingStyles[nesting];
|
|
45
|
+
}
|
|
46
|
+
if (!validStyles.includes(expectedStyle)) {
|
|
47
|
+
expectedStyle = validStyles[0];
|
|
48
|
+
}
|
|
49
|
+
let range = null;
|
|
50
|
+
let fixInfo = null;
|
|
51
|
+
const match = item.line.match(listItemMarkerRe);
|
|
52
|
+
if (match) {
|
|
53
|
+
const column = match.index + 1;
|
|
54
|
+
const length = match[0].length;
|
|
55
|
+
range = [ column, length ];
|
|
56
|
+
fixInfo = {
|
|
57
|
+
"editColumn": match[1].length + 1,
|
|
58
|
+
"deleteCount": 1,
|
|
59
|
+
"insertText": expectedStyleToMarker[expectedStyle]
|
|
60
|
+
};
|
|
38
61
|
}
|
|
62
|
+
addErrorDetailIf(
|
|
63
|
+
onError,
|
|
64
|
+
item.lineNumber,
|
|
65
|
+
expectedStyle,
|
|
66
|
+
itemStyle,
|
|
67
|
+
null,
|
|
68
|
+
null,
|
|
69
|
+
range,
|
|
70
|
+
fixInfo
|
|
71
|
+
);
|
|
39
72
|
});
|
|
40
73
|
}
|
|
41
74
|
});
|
package/lib/md006.js
CHANGED
package/lib/md007.js
CHANGED
|
@@ -13,12 +13,14 @@ module.exports = {
|
|
|
13
13
|
"function": function MD007(params, onError) {
|
|
14
14
|
const indent = Number(params.config.indent || 2);
|
|
15
15
|
const startIndented = !!params.config.start_indented;
|
|
16
|
+
const startIndent = Number(params.config.start_indent || indent);
|
|
16
17
|
flattenedLists().forEach((list) => {
|
|
17
18
|
if (list.unordered && list.parentsUnordered) {
|
|
18
19
|
list.items.forEach((item) => {
|
|
19
20
|
const { lineNumber, line } = item;
|
|
20
|
-
const
|
|
21
|
-
|
|
21
|
+
const expectedIndent =
|
|
22
|
+
(startIndented ? startIndent : 0) +
|
|
23
|
+
(list.nesting * indent);
|
|
22
24
|
const actualIndent = indentFor(item);
|
|
23
25
|
let range = null;
|
|
24
26
|
let editColumn = 1;
|
package/lib/md009.js
CHANGED
|
@@ -49,26 +49,30 @@ module.exports = {
|
|
|
49
49
|
const expected = (brSpaces < 2) ? 0 : brSpaces;
|
|
50
50
|
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
|
51
51
|
const lineNumber = lineIndex + 1;
|
|
52
|
-
const trailingSpaces = line.length - line.
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
52
|
+
const trailingSpaces = line.length - line.trimEnd().length;
|
|
53
|
+
if (
|
|
54
|
+
trailingSpaces &&
|
|
55
|
+
!inCode &&
|
|
56
|
+
!includesSorted(listItemLineNumbers, lineNumber) &&
|
|
57
|
+
(
|
|
58
|
+
(expected !== trailingSpaces) ||
|
|
59
|
+
(strict &&
|
|
60
|
+
(!includesSorted(paragraphLineNumbers, lineNumber) ||
|
|
61
|
+
includesSorted(codeInlineLineNumbers, lineNumber)))
|
|
62
|
+
)
|
|
63
|
+
) {
|
|
64
|
+
const column = line.length - trailingSpaces + 1;
|
|
65
|
+
addError(
|
|
66
|
+
onError,
|
|
67
|
+
lineNumber,
|
|
68
|
+
"Expected: " + (expected === 0 ? "" : "0 or ") +
|
|
69
|
+
expected + "; Actual: " + trailingSpaces,
|
|
70
|
+
null,
|
|
71
|
+
[ column, trailingSpaces ],
|
|
72
|
+
{
|
|
73
|
+
"editColumn": column,
|
|
74
|
+
"deleteCount": trailingSpaces
|
|
75
|
+
});
|
|
72
76
|
}
|
|
73
77
|
});
|
|
74
78
|
}
|
package/lib/md010.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const { addError, forEachLine } = require("../helpers");
|
|
6
|
-
const { lineMetadata } = require("./cache");
|
|
5
|
+
const { addError, forEachLine, overlapsAnyRange } = require("../helpers");
|
|
6
|
+
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
|
|
7
7
|
|
|
8
8
|
const tabRe = /\t+/g;
|
|
9
9
|
|
|
@@ -13,24 +13,33 @@ module.exports = {
|
|
|
13
13
|
"tags": [ "whitespace", "hard_tab" ],
|
|
14
14
|
"function": function MD010(params, onError) {
|
|
15
15
|
const codeBlocks = params.config.code_blocks;
|
|
16
|
-
const
|
|
16
|
+
const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks;
|
|
17
|
+
const spacesPerTab = params.config.spaces_per_tab;
|
|
18
|
+
const spaceMultiplier = (spacesPerTab === undefined) ?
|
|
19
|
+
1 :
|
|
20
|
+
Math.max(0, Number(spacesPerTab));
|
|
21
|
+
const exclusions = includeCode ? [] : codeBlockAndSpanRanges();
|
|
17
22
|
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
|
18
|
-
if (
|
|
23
|
+
if (includeCode || !inCode) {
|
|
19
24
|
let match = null;
|
|
20
25
|
while ((match = tabRe.exec(line)) !== null) {
|
|
21
|
-
const
|
|
26
|
+
const { index } = match;
|
|
27
|
+
const column = index + 1;
|
|
22
28
|
const length = match[0].length;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
if (!overlapsAnyRange(exclusions, lineIndex, index, length)) {
|
|
30
|
+
addError(
|
|
31
|
+
onError,
|
|
32
|
+
lineIndex + 1,
|
|
33
|
+
"Column: " + column,
|
|
34
|
+
null,
|
|
35
|
+
[ column, length ],
|
|
36
|
+
{
|
|
37
|
+
"editColumn": column,
|
|
38
|
+
"deleteCount": length,
|
|
39
|
+
"insertText": "".padEnd(length * spaceMultiplier)
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
});
|
package/lib/md011.js
CHANGED
|
@@ -2,40 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const { addError,
|
|
6
|
-
|
|
5
|
+
const { addError, forEachLine, overlapsAnyRange } = require("../helpers");
|
|
6
|
+
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
|
|
7
7
|
|
|
8
|
-
const reversedLinkRe =
|
|
8
|
+
const reversedLinkRe =
|
|
9
|
+
/(^|[^\\])\(([^)]+)\)\[([^\]^][^\]]*)](?!\()/g;
|
|
9
10
|
|
|
10
11
|
module.exports = {
|
|
11
12
|
"names": [ "MD011", "no-reversed-links" ],
|
|
12
13
|
"description": "Reversed link syntax",
|
|
13
14
|
"tags": [ "links" ],
|
|
14
15
|
"function": function MD011(params, onError) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
16
|
+
const exclusions = codeBlockAndSpanRanges();
|
|
17
|
+
forEachLine(lineMetadata(), (line, lineIndex, inCode, onFence) => {
|
|
18
|
+
if (!inCode && !onFence) {
|
|
19
|
+
let match = null;
|
|
20
|
+
while ((match = reversedLinkRe.exec(line)) !== null) {
|
|
21
|
+
const [ reversedLink, preChar, linkText, linkDestination ] = match;
|
|
22
|
+
const index = match.index + preChar.length;
|
|
23
|
+
const length = match[0].length - preChar.length;
|
|
24
|
+
if (
|
|
25
|
+
!linkText.endsWith("\\") &&
|
|
26
|
+
!linkDestination.endsWith("\\") &&
|
|
27
|
+
!overlapsAnyRange(exclusions, lineIndex, index, length)
|
|
28
|
+
) {
|
|
29
|
+
addError(
|
|
30
|
+
onError,
|
|
31
|
+
lineIndex + 1,
|
|
32
|
+
reversedLink.slice(preChar.length),
|
|
33
|
+
null,
|
|
34
|
+
[ index + 1, length ],
|
|
35
|
+
{
|
|
36
|
+
"editColumn": index + 1,
|
|
37
|
+
"deleteCount": length,
|
|
38
|
+
"insertText": `[${linkText}](${linkDestination})`
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
});
|
|
41
45
|
}
|
package/lib/md018.js
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = {
|
|
|
12
12
|
"function": function MD018(params, onError) {
|
|
13
13
|
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
|
14
14
|
if (!inCode &&
|
|
15
|
-
/^#+[
|
|
15
|
+
/^#+[^# \t]/.test(line) &&
|
|
16
16
|
!/#\s*$/.test(line) &&
|
|
17
17
|
!line.startsWith("#️⃣")) {
|
|
18
18
|
const hashCount = /^#+/.exec(line)[0].length;
|
package/lib/md019.js
CHANGED
|
@@ -13,7 +13,7 @@ module.exports = {
|
|
|
13
13
|
filterTokens(params, "heading_open", (token) => {
|
|
14
14
|
if (headingStyleFor(token) === "atx") {
|
|
15
15
|
const { line, lineNumber } = token;
|
|
16
|
-
const match = /^(#+)(\
|
|
16
|
+
const match = /^(#+)([ \t]{2,})(?:\S)/.exec(line);
|
|
17
17
|
if (match) {
|
|
18
18
|
const [
|
|
19
19
|
,
|
package/lib/md020.js
CHANGED
|
@@ -13,7 +13,7 @@ module.exports = {
|
|
|
13
13
|
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
|
14
14
|
if (!inCode) {
|
|
15
15
|
const match =
|
|
16
|
-
/^(#+)(\
|
|
16
|
+
/^(#+)([ \t]*)([^#]*?[^#\\])([ \t]*)((?:\\#)?)(#+)(\s*)$/.exec(line);
|
|
17
17
|
if (match) {
|
|
18
18
|
const [
|
|
19
19
|
,
|
package/lib/md021.js
CHANGED
|
@@ -13,7 +13,7 @@ module.exports = {
|
|
|
13
13
|
filterTokens(params, "heading_open", (token) => {
|
|
14
14
|
if (headingStyleFor(token) === "atx_closed") {
|
|
15
15
|
const { line, lineNumber } = token;
|
|
16
|
-
const match = /^(#+)(\
|
|
16
|
+
const match = /^(#+)([ \t]+)([^#]+?)([ \t]+)(#+)(\s*)$/.exec(line);
|
|
17
17
|
if (match) {
|
|
18
18
|
const [
|
|
19
19
|
,
|
package/lib/md023.js
CHANGED
|
@@ -17,7 +17,7 @@ module.exports = {
|
|
|
17
17
|
if (match) {
|
|
18
18
|
const [ prefixAndFirstChar, prefix ] = match;
|
|
19
19
|
let deleteCount = prefix.length;
|
|
20
|
-
const prefixLengthNoSpace = prefix.
|
|
20
|
+
const prefixLengthNoSpace = prefix.trimEnd().length;
|
|
21
21
|
if (prefixLengthNoSpace) {
|
|
22
22
|
deleteCount -= prefixLengthNoSpace - 1;
|
|
23
23
|
}
|
package/lib/md025.js
CHANGED
|
@@ -7,7 +7,7 @@ const { addErrorContext, filterTokens, frontMatterHasTitle } =
|
|
|
7
7
|
|
|
8
8
|
module.exports = {
|
|
9
9
|
"names": [ "MD025", "single-title", "single-h1" ],
|
|
10
|
-
"description": "Multiple top
|
|
10
|
+
"description": "Multiple top-level headings in the same document",
|
|
11
11
|
"tags": [ "headings", "headers" ],
|
|
12
12
|
"function": function MD025(params, onError) {
|
|
13
13
|
const level = Number(params.config.level || 1);
|
package/lib/md032.js
CHANGED
|
@@ -17,7 +17,7 @@ module.exports = {
|
|
|
17
17
|
const firstIndex = list.open.map[0];
|
|
18
18
|
if (!isBlankLine(lines[firstIndex - 1])) {
|
|
19
19
|
const line = lines[firstIndex];
|
|
20
|
-
const quotePrefix = line.match(quotePrefixRe)[0].
|
|
20
|
+
const quotePrefix = line.match(quotePrefixRe)[0].trimEnd();
|
|
21
21
|
addErrorContext(
|
|
22
22
|
onError,
|
|
23
23
|
firstIndex + 1,
|
|
@@ -32,7 +32,7 @@ module.exports = {
|
|
|
32
32
|
const lastIndex = list.lastLineIndex - 1;
|
|
33
33
|
if (!isBlankLine(lines[lastIndex + 1])) {
|
|
34
34
|
const line = lines[lastIndex];
|
|
35
|
-
const quotePrefix = line.match(quotePrefixRe)[0].
|
|
35
|
+
const quotePrefix = line.match(quotePrefixRe)[0].trimEnd();
|
|
36
36
|
addErrorContext(
|
|
37
37
|
onError,
|
|
38
38
|
lastIndex + 1,
|
package/lib/md033.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const {
|
|
6
|
-
|
|
5
|
+
const {
|
|
6
|
+
addError, forEachLine, overlapsAnyRange, unescapeMarkdown
|
|
7
|
+
} = require("../helpers");
|
|
8
|
+
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
|
|
7
9
|
|
|
8
10
|
const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g;
|
|
9
11
|
const linkDestinationRe = /]\(\s*$/;
|
|
10
|
-
const inlineCodeRe = /^[^`]*(`+[^`]+`+[^`]+)*`+[^`]*$/;
|
|
11
12
|
// See https://spec.commonmark.org/0.29/#autolinks
|
|
12
13
|
const emailAddressRe =
|
|
13
14
|
// eslint-disable-next-line max-len
|
|
@@ -21,19 +22,22 @@ module.exports = {
|
|
|
21
22
|
let allowedElements = params.config.allowed_elements;
|
|
22
23
|
allowedElements = Array.isArray(allowedElements) ? allowedElements : [];
|
|
23
24
|
allowedElements = allowedElements.map((element) => element.toLowerCase());
|
|
25
|
+
const exclusions = codeBlockAndSpanRanges();
|
|
24
26
|
forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
|
|
25
27
|
let match = null;
|
|
26
28
|
// eslint-disable-next-line no-unmodified-loop-condition
|
|
27
29
|
while (!inCode && ((match = htmlElementRe.exec(line)) !== null)) {
|
|
28
30
|
const [ tag, content, element ] = match;
|
|
29
|
-
if (
|
|
31
|
+
if (
|
|
32
|
+
!allowedElements.includes(element.toLowerCase()) &&
|
|
30
33
|
!tag.endsWith("\\>") &&
|
|
31
|
-
!emailAddressRe.test(content)
|
|
34
|
+
!emailAddressRe.test(content) &&
|
|
35
|
+
!overlapsAnyRange(exclusions, lineIndex, match.index, match[0].length)
|
|
36
|
+
) {
|
|
32
37
|
const prefix = line.substring(0, match.index);
|
|
33
|
-
if (!linkDestinationRe.test(prefix)
|
|
38
|
+
if (!linkDestinationRe.test(prefix)) {
|
|
34
39
|
const unescaped = unescapeMarkdown(prefix + "<", "_");
|
|
35
|
-
if (!unescaped.endsWith("_")
|
|
36
|
-
((unescaped + "`").match(/`/g).length % 2)) {
|
|
40
|
+
if (!unescaped.endsWith("_")) {
|
|
37
41
|
addError(onError, lineIndex + 1, "Element: " + element,
|
|
38
42
|
null, [ match.index + 1, tag.length ]);
|
|
39
43
|
}
|
package/lib/md035.js
CHANGED
|
@@ -10,12 +10,12 @@ module.exports = {
|
|
|
10
10
|
"tags": [ "hr" ],
|
|
11
11
|
"function": function MD035(params, onError) {
|
|
12
12
|
let style = String(params.config.style || "consistent");
|
|
13
|
-
filterTokens(params, "hr",
|
|
14
|
-
const
|
|
13
|
+
filterTokens(params, "hr", (token) => {
|
|
14
|
+
const { lineNumber, markup } = token;
|
|
15
15
|
if (style === "consistent") {
|
|
16
|
-
style =
|
|
16
|
+
style = markup;
|
|
17
17
|
}
|
|
18
|
-
addErrorDetailIf(onError,
|
|
18
|
+
addErrorDetailIf(onError, lineNumber, style, markup);
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
};
|
package/lib/md037.js
CHANGED
|
@@ -7,9 +7,11 @@ const { addErrorContext, emphasisMarkersInContent, forEachLine, isBlankLine } =
|
|
|
7
7
|
const { lineMetadata } = require("./cache");
|
|
8
8
|
|
|
9
9
|
const emphasisRe = /(^|[^\\]|\\\\)(?:(\*\*?\*?)|(__?_?))/g;
|
|
10
|
+
const embeddedUnderscoreRe = /([A-Za-z0-9])_([A-Za-z0-9])/g;
|
|
10
11
|
const asteriskListItemMarkerRe = /^([\s>]*)\*(\s+)/;
|
|
11
12
|
const leftSpaceRe = /^\s+/;
|
|
12
13
|
const rightSpaceRe = /\s+$/;
|
|
14
|
+
const tablePipeRe = /\|/;
|
|
13
15
|
|
|
14
16
|
module.exports = {
|
|
15
17
|
"names": [ "MD037", "no-space-in-emphasis" ],
|
|
@@ -28,18 +30,23 @@ module.exports = {
|
|
|
28
30
|
pendingError = null;
|
|
29
31
|
}
|
|
30
32
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
31
|
-
function handleRunEnd(
|
|
33
|
+
function handleRunEnd(
|
|
34
|
+
line, lineIndex, contextLength, match, matchIndex, inTable
|
|
35
|
+
) {
|
|
32
36
|
// Close current run
|
|
33
37
|
let content = line.substring(emphasisIndex, matchIndex);
|
|
34
38
|
if (!emphasisLength) {
|
|
35
|
-
content = content.
|
|
39
|
+
content = content.trimStart();
|
|
36
40
|
}
|
|
37
41
|
if (!match) {
|
|
38
|
-
content = content.
|
|
42
|
+
content = content.trimEnd();
|
|
39
43
|
}
|
|
40
44
|
const leftSpace = leftSpaceRe.test(content);
|
|
41
45
|
const rightSpace = rightSpaceRe.test(content);
|
|
42
|
-
if (
|
|
46
|
+
if (
|
|
47
|
+
(leftSpace || rightSpace) &&
|
|
48
|
+
(!inTable || !tablePipeRe.test(content))
|
|
49
|
+
) {
|
|
43
50
|
// Report the violation
|
|
44
51
|
const contextStart = emphasisIndex - emphasisLength;
|
|
45
52
|
const contextEnd = matchIndex + contextLength;
|
|
@@ -70,24 +77,37 @@ module.exports = {
|
|
|
70
77
|
resetRunTracking();
|
|
71
78
|
forEachLine(
|
|
72
79
|
lineMetadata(),
|
|
73
|
-
(line, lineIndex, inCode, onFence, inTable, inItem, onBreak) => {
|
|
80
|
+
(line, lineIndex, inCode, onFence, inTable, inItem, onBreak, inMath) => {
|
|
74
81
|
const onItemStart = (inItem === 1);
|
|
75
|
-
if (
|
|
82
|
+
if (
|
|
83
|
+
inCode ||
|
|
84
|
+
onFence ||
|
|
85
|
+
inTable ||
|
|
86
|
+
onBreak ||
|
|
87
|
+
onItemStart ||
|
|
88
|
+
isBlankLine(line)
|
|
89
|
+
) {
|
|
76
90
|
// Emphasis resets when leaving a block
|
|
77
91
|
resetRunTracking();
|
|
78
92
|
}
|
|
79
|
-
if (
|
|
93
|
+
if (
|
|
94
|
+
inCode ||
|
|
95
|
+
onFence ||
|
|
96
|
+
onBreak ||
|
|
97
|
+
inMath
|
|
98
|
+
) {
|
|
80
99
|
// Emphasis has no meaning here
|
|
81
100
|
return;
|
|
82
101
|
}
|
|
102
|
+
let patchedLine = line.replace(embeddedUnderscoreRe, "$1 $2");
|
|
83
103
|
if (onItemStart) {
|
|
84
104
|
// Trim overlapping '*' list item marker
|
|
85
|
-
|
|
105
|
+
patchedLine = patchedLine.replace(asteriskListItemMarkerRe, "$1 $2");
|
|
86
106
|
}
|
|
87
107
|
let match = null;
|
|
88
108
|
// Match all emphasis-looking runs in the line...
|
|
89
|
-
while ((match = emphasisRe.exec(
|
|
90
|
-
const ignoreMarkersForLine = ignoreMarkersByLine[lineIndex]
|
|
109
|
+
while ((match = emphasisRe.exec(patchedLine))) {
|
|
110
|
+
const ignoreMarkersForLine = ignoreMarkersByLine[lineIndex];
|
|
91
111
|
const matchIndex = match.index + match[1].length;
|
|
92
112
|
if (ignoreMarkersForLine.includes(matchIndex)) {
|
|
93
113
|
// Ignore emphasis markers inside code spans and links
|
|
@@ -106,12 +126,20 @@ module.exports = {
|
|
|
106
126
|
if (matchLength === effectiveEmphasisLength) {
|
|
107
127
|
// Ending an existing run, report any pending error
|
|
108
128
|
if (pendingError) {
|
|
129
|
+
// @ts-ignore
|
|
109
130
|
addErrorContext(...pendingError);
|
|
110
131
|
pendingError = null;
|
|
111
132
|
}
|
|
112
133
|
const error = handleRunEnd(
|
|
113
|
-
line,
|
|
134
|
+
line,
|
|
135
|
+
lineIndex,
|
|
136
|
+
effectiveEmphasisLength,
|
|
137
|
+
match,
|
|
138
|
+
matchIndex,
|
|
139
|
+
inTable
|
|
140
|
+
);
|
|
114
141
|
if (error) {
|
|
142
|
+
// @ts-ignore
|
|
115
143
|
addErrorContext(...error);
|
|
116
144
|
}
|
|
117
145
|
// Reset
|
|
@@ -139,7 +167,7 @@ module.exports = {
|
|
|
139
167
|
}
|
|
140
168
|
if (emphasisIndex !== -1) {
|
|
141
169
|
pendingError = pendingError ||
|
|
142
|
-
handleRunEnd(line, lineIndex, 0, null, line.length);
|
|
170
|
+
handleRunEnd(line, lineIndex, 0, null, line.length, inTable);
|
|
143
171
|
// Adjust for pending run on new line
|
|
144
172
|
emphasisIndex = 0;
|
|
145
173
|
emphasisLength = 0;
|
package/lib/md039.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
const { addErrorContext, filterTokens } = require("../helpers");
|
|
6
6
|
|
|
7
|
-
const spaceInLinkRe =
|
|
7
|
+
const spaceInLinkRe =
|
|
8
|
+
/\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=((?:\([^)]*\))|(?:\[[^\]]*\])))/;
|
|
8
9
|
|
|
9
10
|
module.exports = {
|
|
10
11
|
"names": [ "MD039", "no-space-in-links" ],
|
|
@@ -24,8 +25,8 @@ module.exports = {
|
|
|
24
25
|
linkText = "";
|
|
25
26
|
} else if (type === "link_close") {
|
|
26
27
|
inLink = false;
|
|
27
|
-
const left = linkText.
|
|
28
|
-
const right = linkText.
|
|
28
|
+
const left = linkText.trimStart().length !== linkText.length;
|
|
29
|
+
const right = linkText.trimEnd().length !== linkText.length;
|
|
29
30
|
if (left || right) {
|
|
30
31
|
const line = params.lines[lineNumber - 1];
|
|
31
32
|
let range = null;
|
package/lib/md041.js
CHANGED
|
@@ -6,7 +6,7 @@ const { addErrorContext, frontMatterHasTitle } = require("../helpers");
|
|
|
6
6
|
|
|
7
7
|
module.exports = {
|
|
8
8
|
"names": [ "MD041", "first-line-heading", "first-line-h1" ],
|
|
9
|
-
"description": "First line in file should be a top
|
|
9
|
+
"description": "First line in a file should be a top-level heading",
|
|
10
10
|
"tags": [ "headings", "headers" ],
|
|
11
11
|
"function": function MD041(params, onError) {
|
|
12
12
|
const level = Number(params.config.level || 1);
|
|
@@ -17,11 +17,22 @@ module.exports = {
|
|
|
17
17
|
params.config.front_matter_title
|
|
18
18
|
);
|
|
19
19
|
if (!foundFrontMatterTitle) {
|
|
20
|
+
const htmlHeadingRe = new RegExp(`^<h${level}[ />]`, "i");
|
|
20
21
|
params.tokens.every((token) => {
|
|
22
|
+
let isError = false;
|
|
21
23
|
if (token.type === "html_block") {
|
|
22
|
-
|
|
24
|
+
if (token.content.startsWith("<!--")) {
|
|
25
|
+
// Ignore leading HTML comments
|
|
26
|
+
return true;
|
|
27
|
+
} else if (!htmlHeadingRe.test(token.content)) {
|
|
28
|
+
// Something other than an HTML heading
|
|
29
|
+
isError = true;
|
|
30
|
+
}
|
|
31
|
+
} else if ((token.type !== "heading_open") || (token.tag !== tag)) {
|
|
32
|
+
// Something other than a Markdown heading
|
|
33
|
+
isError = true;
|
|
23
34
|
}
|
|
24
|
-
if (
|
|
35
|
+
if (isError) {
|
|
25
36
|
addErrorContext(onError, token.lineNumber, token.line);
|
|
26
37
|
}
|
|
27
38
|
return false;
|
package/lib/md043.js
CHANGED
|
@@ -20,16 +20,18 @@ module.exports = {
|
|
|
20
20
|
let matchAny = false;
|
|
21
21
|
let hasError = false;
|
|
22
22
|
let anyHeadings = false;
|
|
23
|
-
// eslint-disable-next-line func-style
|
|
24
23
|
const getExpected = () => requiredHeadings[i++] || "[None]";
|
|
25
24
|
forEachHeading(params, (heading, content) => {
|
|
26
25
|
if (!hasError) {
|
|
27
26
|
anyHeadings = true;
|
|
28
27
|
const actual = levels[heading.tag] + " " + content;
|
|
29
|
-
|
|
28
|
+
const expected = getExpected();
|
|
30
29
|
if (expected === "*") {
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const nextExpected = getExpected();
|
|
31
|
+
if (nextExpected.toLowerCase() !== actual.toLowerCase()) {
|
|
32
|
+
matchAny = true;
|
|
33
|
+
i--;
|
|
34
|
+
}
|
|
33
35
|
} else if (expected === "+") {
|
|
34
36
|
matchAny = true;
|
|
35
37
|
} else if (expected.toLowerCase() === actual.toLowerCase()) {
|
|
@@ -43,9 +45,11 @@ module.exports = {
|
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
});
|
|
48
|
+
const extraHeadings = requiredHeadings.length - i;
|
|
46
49
|
if (
|
|
47
50
|
!hasError &&
|
|
48
|
-
(
|
|
51
|
+
((extraHeadings > 1) ||
|
|
52
|
+
((extraHeadings === 1) && (requiredHeadings[i] !== "*"))) &&
|
|
49
53
|
(anyHeadings || !requiredHeadings.every((heading) => heading === "*"))
|
|
50
54
|
) {
|
|
51
55
|
addErrorContext(onError, params.lines.length,
|