markdownlint 0.26.2 → 0.28.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +378 -0
  2. package/CONTRIBUTING.md +82 -59
  3. package/LICENSE +1 -1
  4. package/README.md +304 -305
  5. package/demo/markdownlint-browser.js +4973 -3379
  6. package/doc/CustomRules.md +99 -51
  7. package/doc/Prettier.md +14 -10
  8. package/doc/ReleaseProcess.md +20 -0
  9. package/doc/Rules.md +474 -311
  10. package/doc/md001.md +37 -0
  11. package/doc/md002.md +37 -0
  12. package/doc/md003.md +59 -0
  13. package/doc/md004.md +49 -0
  14. package/doc/md005.md +53 -0
  15. package/doc/md006.md +46 -0
  16. package/doc/md007.md +55 -0
  17. package/doc/md009.md +51 -0
  18. package/doc/md010.md +56 -0
  19. package/doc/md011.md +30 -0
  20. package/doc/md012.md +38 -0
  21. package/doc/md013.md +60 -0
  22. package/doc/md014.md +54 -0
  23. package/doc/md018.md +27 -0
  24. package/doc/md019.md +28 -0
  25. package/doc/md020.md +29 -0
  26. package/doc/md021.md +31 -0
  27. package/doc/md022.md +46 -0
  28. package/doc/md023.md +26 -0
  29. package/doc/md024.md +47 -0
  30. package/doc/md025.md +49 -0
  31. package/doc/md026.md +41 -0
  32. package/doc/md027.md +24 -0
  33. package/doc/md028.md +40 -0
  34. package/doc/md029.md +98 -0
  35. package/doc/md030.md +82 -0
  36. package/doc/md031.md +50 -0
  37. package/doc/md032.md +38 -0
  38. package/doc/md033.md +27 -0
  39. package/doc/md034.md +50 -0
  40. package/doc/md035.md +42 -0
  41. package/doc/md036.md +45 -0
  42. package/doc/md037.md +37 -0
  43. package/doc/md038.md +40 -0
  44. package/doc/md039.md +21 -0
  45. package/doc/md040.md +52 -0
  46. package/doc/md041.md +49 -0
  47. package/doc/md042.md +32 -0
  48. package/doc/md043.md +72 -0
  49. package/doc/md044.md +35 -0
  50. package/doc/md045.md +34 -0
  51. package/doc/md046.md +40 -0
  52. package/doc/md047.md +34 -0
  53. package/doc/md048.md +41 -0
  54. package/doc/md049.md +32 -0
  55. package/doc/md050.md +32 -0
  56. package/doc/md051.md +50 -0
  57. package/doc/md052.md +33 -0
  58. package/doc/md053.md +38 -0
  59. package/helpers/LICENSE +1 -1
  60. package/helpers/README.md +35 -15
  61. package/helpers/helpers.js +182 -162
  62. package/helpers/micromark.cjs +209 -0
  63. package/helpers/package.json +5 -2
  64. package/lib/constants.js +8 -1
  65. package/lib/markdownlint.d.ts +5 -5
  66. package/lib/markdownlint.js +46 -24
  67. package/lib/md002.js +1 -1
  68. package/lib/md011.js +2 -2
  69. package/lib/md013.js +7 -6
  70. package/lib/md019.js +1 -1
  71. package/lib/md022.js +15 -3
  72. package/lib/md023.js +1 -1
  73. package/lib/md026.js +1 -1
  74. package/lib/md027.js +1 -1
  75. package/lib/md028.js +1 -1
  76. package/lib/md033.js +47 -29
  77. package/lib/md034.js +54 -47
  78. package/lib/md035.js +9 -11
  79. package/lib/md036.js +1 -1
  80. package/lib/md037.js +2 -2
  81. package/lib/md038.js +81 -56
  82. package/lib/md039.js +1 -1
  83. package/lib/md040.js +21 -2
  84. package/lib/md041.js +1 -1
  85. package/lib/md043.js +4 -2
  86. package/lib/md044.js +58 -43
  87. package/lib/md046.js +1 -1
  88. package/lib/md048.js +3 -1
  89. package/lib/md051.js +37 -5
  90. package/lib/rules.js +1 -1
  91. package/package.json +43 -28
  92. package/schema/.markdownlint.jsonc +11 -4
  93. package/schema/.markdownlint.yaml +11 -6
  94. package/schema/markdownlint-config-schema.json +27 -3
  95. package/style/prettier.json +26 -0
package/lib/md033.js CHANGED
@@ -2,16 +2,11 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const {
6
- addError, forEachLine, htmlElementRe, withinAnyRange, unescapeMarkdown
7
- } = require("../helpers");
8
- const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
5
+ const { addError } = require("../helpers");
6
+ const { filterByTypes, getHtmlTagInfo, parse } =
7
+ require("../helpers/micromark.cjs");
9
8
 
10
- const linkDestinationRe = /]\(\s*$/;
11
- // See https://spec.commonmark.org/0.29/#autolinks
12
- const emailAddressRe =
13
- // eslint-disable-next-line max-len
14
- /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
9
+ const nextLinesRe = /[\r\n][\s\S]*$/;
15
10
 
16
11
  module.exports = {
17
12
  "names": [ "MD033", "no-inline-html" ],
@@ -21,28 +16,51 @@ module.exports = {
21
16
  let allowedElements = params.config.allowed_elements;
22
17
  allowedElements = Array.isArray(allowedElements) ? allowedElements : [];
23
18
  allowedElements = allowedElements.map((element) => element.toLowerCase());
24
- const exclusions = codeBlockAndSpanRanges();
25
- forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
26
- let match = null;
27
- // eslint-disable-next-line no-unmodified-loop-condition
28
- while (!inCode && ((match = htmlElementRe.exec(line)) !== null)) {
29
- const [ tag, content, element ] = match;
30
- if (
31
- !allowedElements.includes(element.toLowerCase()) &&
32
- !tag.endsWith("\\>") &&
33
- !emailAddressRe.test(content) &&
34
- !withinAnyRange(exclusions, lineIndex, match.index, match[0].length)
35
- ) {
36
- const prefix = line.substring(0, match.index);
37
- if (!linkDestinationRe.test(prefix)) {
38
- const unescaped = unescapeMarkdown(prefix + "<", "_");
39
- if (!unescaped.endsWith("_")) {
40
- addError(onError, lineIndex + 1, "Element: " + element,
41
- undefined, [ match.index + 1, tag.length ]);
42
- }
19
+ const pending = [ [ 0, params.parsers.micromark.tokens ] ];
20
+ let current = null;
21
+ while ((current = pending.shift())) {
22
+ const [ offset, tokens ] = current;
23
+ for (const token of filterByTypes(tokens, [ "htmlFlow", "htmlText" ])) {
24
+ if (token.type === "htmlText") {
25
+ const htmlTagInfo = getHtmlTagInfo(token);
26
+ if (
27
+ htmlTagInfo &&
28
+ !htmlTagInfo.close &&
29
+ !allowedElements.includes(htmlTagInfo.name.toLowerCase())
30
+ ) {
31
+ const range = [
32
+ token.startColumn,
33
+ token.text.replace(nextLinesRe, "").length
34
+ ];
35
+ addError(
36
+ onError,
37
+ token.startLine + offset,
38
+ "Element: " + htmlTagInfo.name,
39
+ undefined,
40
+ range
41
+ );
43
42
  }
43
+ } else {
44
+ // token.type === "htmlFlow"
45
+ // Re-parse without "htmlFlow" to get only "htmlText" tokens
46
+ const options = {
47
+ "extensions": [
48
+ {
49
+ "disable": {
50
+ "null": [ "codeIndented", "htmlFlow" ]
51
+ }
52
+ }
53
+ ]
54
+ };
55
+ // Use lines instead of token.text for accurate columns
56
+ const lines =
57
+ params.lines.slice(token.startLine - 1, token.endLine).join("\n");
58
+ const flowTokens = parse(lines, options);
59
+ pending.push(
60
+ [ token.startLine - 1, flowTokens ]
61
+ );
44
62
  }
45
63
  }
46
- });
64
+ }
47
65
  }
48
66
  };
package/lib/md034.js CHANGED
@@ -2,60 +2,67 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorContext, bareUrlRe, filterTokens } = require("../helpers");
5
+ const { addErrorContext } = require("../helpers");
6
+ const { filterByPredicate, getHtmlTagInfo } =
7
+ require("../helpers/micromark.cjs");
6
8
 
7
9
  module.exports = {
8
10
  "names": [ "MD034", "no-bare-urls" ],
9
11
  "description": "Bare URL used",
10
12
  "tags": [ "links", "url" ],
11
13
  "function": function MD034(params, onError) {
12
- filterTokens(params, "inline", (token) => {
13
- let inLink = false;
14
- for (const child of token.children) {
15
- const { content, line, lineNumber, type } = child;
16
- let match = null;
17
- if (type === "link_open") {
18
- inLink = true;
19
- } else if (type === "link_close") {
20
- inLink = false;
21
- } else if ((type === "text") && !inLink) {
22
- while ((match = bareUrlRe.exec(content)) !== null) {
23
- const [ bareUrl ] = match;
24
- const matchIndex = match.index;
25
- const bareUrlLength = bareUrl.length;
26
- // Allow "[https://example.com]" to avoid conflicts with
27
- // MD011/no-reversed-links; allow quoting as another way
28
- // of deliberately including a bare URL
29
- const leftChar = content[matchIndex - 1];
30
- const rightChar = content[matchIndex + bareUrlLength];
31
- if (
32
- !((leftChar === "[") && (rightChar === "]")) &&
33
- !((leftChar === "\"") && (rightChar === "\"")) &&
34
- !((leftChar === "'") && (rightChar === "'"))
35
- ) {
36
- const index = line.indexOf(content);
37
- const range = (index === -1) ? null : [
38
- index + matchIndex + 1,
39
- bareUrlLength
40
- ];
41
- const fixInfo = range ? {
42
- "editColumn": range[0],
43
- "deleteCount": range[1],
44
- "insertText": `<${bareUrl}>`
45
- } : null;
46
- addErrorContext(
47
- onError,
48
- lineNumber,
49
- bareUrl,
50
- null,
51
- null,
52
- range,
53
- fixInfo
54
- );
14
+ const literalAutolinks =
15
+ filterByPredicate(
16
+ params.parsers.micromark.tokens,
17
+ (token) => token.type === "literalAutolink",
18
+ (token) => {
19
+ const { children } = token;
20
+ const result = [];
21
+ for (let i = 0; i < children.length; i++) {
22
+ const openToken = children[i];
23
+ const openTagInfo = getHtmlTagInfo(openToken);
24
+ if (openTagInfo && !openTagInfo.close) {
25
+ let count = 1;
26
+ for (let j = i + 1; j < children.length; j++) {
27
+ const closeToken = children[j];
28
+ const closeTagInfo = getHtmlTagInfo(closeToken);
29
+ if (closeTagInfo && (openTagInfo.name === closeTagInfo.name)) {
30
+ if (closeTagInfo.close) {
31
+ count--;
32
+ if (count === 0) {
33
+ i = j;
34
+ break;
35
+ }
36
+ } else {
37
+ count++;
38
+ }
39
+ }
40
+ }
41
+ } else {
42
+ result.push(openToken);
55
43
  }
56
44
  }
57
- }
58
- }
59
- });
45
+ return result;
46
+ });
47
+ for (const token of literalAutolinks) {
48
+ const range = [
49
+ token.startColumn,
50
+ token.endColumn - token.startColumn
51
+ ];
52
+ const fixInfo = {
53
+ "editColumn": range[0],
54
+ "deleteCount": range[1],
55
+ "insertText": `<${token.text}>`
56
+ };
57
+ addErrorContext(
58
+ onError,
59
+ token.startLine,
60
+ token.text,
61
+ null,
62
+ null,
63
+ range,
64
+ fixInfo
65
+ );
66
+ }
60
67
  }
61
68
  };
package/lib/md035.js CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorDetailIf, filterTokens } = require("../helpers");
5
+ const { addErrorDetailIf } = require("../helpers");
6
+ const { filterByTypes } = require("../helpers/micromark.cjs");
6
7
 
7
8
  module.exports = {
8
9
  "names": [ "MD035", "hr-style" ],
@@ -10,17 +11,14 @@ module.exports = {
10
11
  "tags": [ "hr" ],
11
12
  "function": function MD035(params, onError) {
12
13
  let style = String(params.config.style || "consistent").trim();
13
- filterTokens(params, "hr", (token) => {
14
- const { line, lineNumber } = token;
15
- let { markup } = token;
16
- const match = line.match(/[_*\-\s\t]+$/);
17
- if (match) {
18
- markup = match[0].trim();
19
- }
14
+ const thematicBreaks =
15
+ filterByTypes(params.parsers.micromark.tokens, [ "thematicBreak" ]);
16
+ for (const token of thematicBreaks) {
17
+ const { startLine, text } = token;
20
18
  if (style === "consistent") {
21
- style = markup;
19
+ style = text;
22
20
  }
23
- addErrorDetailIf(onError, lineNumber, style, markup);
24
- });
21
+ addErrorDetailIf(onError, startLine, style, text);
22
+ }
25
23
  }
26
24
  };
package/lib/md036.js CHANGED
@@ -49,7 +49,7 @@ module.exports = {
49
49
  return base;
50
50
  }
51
51
  let state = base;
52
- for (const token of params.tokens) {
52
+ for (const token of params.parsers.markdownit.tokens) {
53
53
  state = state(token);
54
54
  }
55
55
  }
package/lib/md037.js CHANGED
@@ -6,8 +6,8 @@ const { addErrorContext, emphasisMarkersInContent, forEachLine, isBlankLine,
6
6
  withinAnyRange } = require("../helpers");
7
7
  const { htmlElementRanges, lineMetadata } = require("./cache");
8
8
 
9
- const emphasisRe = /(^|[^\\]|\\\\)(?:(\*\*?\*?)|(__?_?))/g;
10
- const embeddedUnderscoreRe = /([A-Za-z0-9])_([A-Za-z0-9])/g;
9
+ const emphasisRe = /(^|[^\\]|\\\\)(?:(\*{1,3})|(_{1,3}))/g;
10
+ const embeddedUnderscoreRe = /([A-Za-z\d])_([A-Za-z\d])/g;
11
11
  const asteriskListItemMarkerRe = /^([\s>]*)\*(\s+)/;
12
12
  const leftSpaceRe = /^\s+/;
13
13
  const rightSpaceRe = /\s+$/;
package/lib/md038.js CHANGED
@@ -2,70 +2,95 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorContext, filterTokens, forEachInlineCodeSpan, newLineRe } =
6
- require("../helpers");
5
+ const { addErrorContext } = require("../helpers");
6
+ const { filterByTypes } = require("../helpers/micromark.cjs");
7
7
 
8
- const leftSpaceRe = /^\s([^`]|$)/;
8
+ const leftSpaceRe = /^\s(?:[^`]|$)/;
9
9
  const rightSpaceRe = /[^`]\s$/;
10
-
11
- const spaceInsideCodeInline = (token) => (
12
- (token.type === "code_inline") &&
13
- (leftSpaceRe.test(token.content) || rightSpaceRe.test(token.content))
14
- );
10
+ const trimCodeText = (text, start, end) => {
11
+ text = text.replace(/^\s+$/, "");
12
+ if (start) {
13
+ text = text.replace(/^\s+?(\s`|\S)/, "$1");
14
+ }
15
+ if (end) {
16
+ text = text.replace(/(`\s|\S)\s+$/, "$1");
17
+ }
18
+ return text;
19
+ };
20
+ const tokenIfType = (token, type) => token && (token.type === type) && token;
15
21
 
16
22
  module.exports = {
17
23
  "names": [ "MD038", "no-space-in-code" ],
18
24
  "description": "Spaces inside code span elements",
19
25
  "tags": [ "whitespace", "code" ],
20
26
  "function": function MD038(params, onError) {
21
- filterTokens(params, "inline", (token) => {
22
- if (token.children.some(spaceInsideCodeInline)) {
23
- const tokenLines = params.lines.slice(token.map[0], token.map[1]);
24
- forEachInlineCodeSpan(
25
- tokenLines.join("\n"),
26
- (code, lineIndex, columnIndex, tickCount) => {
27
- let rangeIndex = columnIndex - tickCount;
28
- let rangeLength = code.length + (2 * tickCount);
29
- let rangeLineOffset = 0;
30
- let fixIndex = columnIndex;
31
- let fixLength = code.length;
32
- const codeLines = code.split(newLineRe);
33
- const left = leftSpaceRe.test(code);
34
- const right = !left && rightSpaceRe.test(code);
35
- if (right && (codeLines.length > 1)) {
36
- rangeIndex = 0;
37
- rangeLineOffset = codeLines.length - 1;
38
- fixIndex = 0;
39
- }
40
- if (left || right) {
41
- const codeLinesRange = codeLines[rangeLineOffset];
42
- if (codeLines.length > 1) {
43
- rangeLength = codeLinesRange.length + tickCount;
44
- fixLength = codeLinesRange.length;
45
- }
46
- const context = tokenLines[lineIndex + rangeLineOffset]
47
- .substring(rangeIndex, rangeIndex + rangeLength);
48
- const codeLinesRangeTrim = codeLinesRange.trim();
49
- const fixText =
50
- (codeLinesRangeTrim.startsWith("`") ? " " : "") +
51
- codeLinesRangeTrim +
52
- (codeLinesRangeTrim.endsWith("`") ? " " : "");
53
- addErrorContext(
54
- onError,
55
- token.lineNumber + lineIndex + rangeLineOffset,
56
- context,
57
- left,
58
- right,
59
- [ rangeIndex + 1, rangeLength ],
60
- {
61
- "editColumn": fixIndex + 1,
62
- "deleteCount": fixLength,
63
- "insertText": fixText
64
- }
65
- );
66
- }
67
- });
27
+ const codeTextTokens =
28
+ filterByTypes(params.parsers.micromark.tokens, [ "codeText" ]);
29
+ for (const token of codeTextTokens) {
30
+ const { children } = token;
31
+ const first = 0;
32
+ const last = children.length - 1;
33
+ const startSequence = tokenIfType(children[first], "codeTextSequence");
34
+ const endSequence = tokenIfType(children[last], "codeTextSequence");
35
+ const startData =
36
+ tokenIfType(children[first + 1], "codeTextData") ||
37
+ tokenIfType(children[first + 2], "codeTextData");
38
+ const endData =
39
+ tokenIfType(children[last - 1], "codeTextData") ||
40
+ tokenIfType(children[last - 2], "codeTextData");
41
+ if (startSequence && endSequence && startData && endData) {
42
+ const spaceLeft = leftSpaceRe.test(startData.text);
43
+ const spaceRight = !spaceLeft && rightSpaceRe.test(endData.text);
44
+ if (spaceLeft || spaceRight) {
45
+ let lineNumber = startSequence.startLine;
46
+ let range = null;
47
+ let fixInfo = null;
48
+ if (startSequence.startLine === endSequence.endLine) {
49
+ range = [
50
+ startSequence.startColumn,
51
+ endSequence.endColumn - startSequence.startColumn
52
+ ];
53
+ fixInfo = {
54
+ "editColumn": startSequence.endColumn,
55
+ "deleteCount": endSequence.startColumn - startSequence.endColumn,
56
+ "insertText": trimCodeText(startData.text, true, true)
57
+ };
58
+ } else if (spaceLeft) {
59
+ range = [
60
+ startSequence.startColumn,
61
+ startData.endColumn - startSequence.startColumn
62
+ ];
63
+ fixInfo = {
64
+ "editColumn": startSequence.endColumn,
65
+ "deleteCount": startData.endColumn - startData.startColumn,
66
+ "insertText": trimCodeText(startData.text, true, false)
67
+ };
68
+ } else {
69
+ lineNumber = endSequence.endLine;
70
+ range = [
71
+ endData.startColumn,
72
+ endSequence.endColumn - endData.startColumn
73
+ ];
74
+ fixInfo = {
75
+ "editColumn": endData.startColumn,
76
+ "deleteCount": endData.endColumn - endData.startColumn,
77
+ "insertText": trimCodeText(endData.text, false, true)
78
+ };
79
+ }
80
+ const context = params
81
+ .lines[lineNumber - 1]
82
+ .substring(range[0] - 1, range[0] - 1 + range[1]);
83
+ addErrorContext(
84
+ onError,
85
+ lineNumber,
86
+ context,
87
+ spaceLeft,
88
+ spaceRight,
89
+ range,
90
+ fixInfo
91
+ );
92
+ }
68
93
  }
69
- });
94
+ }
70
95
  }
71
96
  };
package/lib/md039.js CHANGED
@@ -5,7 +5,7 @@
5
5
  const { addErrorContext, filterTokens } = require("../helpers");
6
6
 
7
7
  const spaceInLinkRe =
8
- /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=((?:\([^)]*\))|(?:\[[^\]]*\])))/;
8
+ /\[(?:\s[^\]]*|[^\]]*?\s)\](?=(\([^)]*\)|\[[^\]]*\]))/;
9
9
 
10
10
  module.exports = {
11
11
  "names": [ "MD039", "no-space-in-links" ],
package/lib/md040.js CHANGED
@@ -2,16 +2,35 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorContext, filterTokens } = require("../helpers");
5
+ const { addError, addErrorContext, filterTokens } = require("../helpers");
6
6
 
7
7
  module.exports = {
8
8
  "names": [ "MD040", "fenced-code-language" ],
9
9
  "description": "Fenced code blocks should have a language specified",
10
10
  "tags": [ "code", "language" ],
11
11
  "function": function MD040(params, onError) {
12
+ let allowed = params.config.allowed_languages;
13
+ allowed = Array.isArray(allowed) ? allowed : [];
14
+ const languageOnly = !!params.config.language_only;
15
+
12
16
  filterTokens(params, "fence", function forToken(token) {
13
- if (!token.info.trim()) {
17
+ const lang = token.info.trim().split(/\s+/u).shift();
18
+ if (lang === "") {
14
19
  addErrorContext(onError, token.lineNumber, token.line);
20
+ } else if ((allowed.length > 0) && !allowed.includes(lang)) {
21
+ addError(
22
+ onError,
23
+ token.lineNumber,
24
+ `"${lang}" is not allowed`
25
+ );
26
+ }
27
+
28
+ if (languageOnly && (token.info !== lang)) {
29
+ addError(
30
+ onError,
31
+ token.lineNumber,
32
+ `Info string contains more than language: "${token.info}"`
33
+ );
15
34
  }
16
35
  });
17
36
  }
package/lib/md041.js CHANGED
@@ -18,7 +18,7 @@ module.exports = {
18
18
  );
19
19
  if (!foundFrontMatterTitle) {
20
20
  const htmlHeadingRe = new RegExp(`^<h${level}[ />]`, "i");
21
- params.tokens.every((token) => {
21
+ params.parsers.markdownit.tokens.every((token) => {
22
22
  let isError = false;
23
23
  if (token.type === "html_block") {
24
24
  if (token.content.startsWith("<!--")) {
package/lib/md043.js CHANGED
@@ -11,6 +11,7 @@ module.exports = {
11
11
  "tags": [ "headings", "headers" ],
12
12
  "function": function MD043(params, onError) {
13
13
  const requiredHeadings = params.config.headings || params.config.headers;
14
+ const matchCase = params.config.match_case || false;
14
15
  if (Array.isArray(requiredHeadings)) {
15
16
  const levels = {};
16
17
  for (const level of [ 1, 2, 3, 4, 5, 6 ]) {
@@ -21,6 +22,7 @@ module.exports = {
21
22
  let hasError = false;
22
23
  let anyHeadings = false;
23
24
  const getExpected = () => requiredHeadings[i++] || "[None]";
25
+ const handleCase = (str) => (matchCase ? str : str.toLowerCase());
24
26
  forEachHeading(params, (heading, content) => {
25
27
  if (!hasError) {
26
28
  anyHeadings = true;
@@ -28,13 +30,13 @@ module.exports = {
28
30
  const expected = getExpected();
29
31
  if (expected === "*") {
30
32
  const nextExpected = getExpected();
31
- if (nextExpected.toLowerCase() !== actual.toLowerCase()) {
33
+ if (handleCase(nextExpected) !== handleCase(actual)) {
32
34
  matchAny = true;
33
35
  i--;
34
36
  }
35
37
  } else if (expected === "+") {
36
38
  matchAny = true;
37
- } else if (expected.toLowerCase() === actual.toLowerCase()) {
39
+ } else if (handleCase(expected) === handleCase(actual)) {
38
40
  matchAny = false;
39
41
  } else if (matchAny) {
40
42
  i--;
package/lib/md044.js CHANGED
@@ -2,11 +2,14 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorDetailIf, bareUrlRe, escapeForRegExp, forEachLine,
6
- forEachLink, withinAnyRange, linkReferenceDefinitionRe } =
5
+ const { addErrorDetailIf, escapeForRegExp, withinAnyRange } =
7
6
  require("../helpers");
8
- const { codeBlockAndSpanRanges, htmlElementRanges, lineMetadata } =
9
- require("./cache");
7
+ const { filterByPredicate, filterByTypes, parse } =
8
+ require("../helpers/micromark.cjs");
9
+
10
+ const ignoredChildTypes = new Set(
11
+ [ "codeFencedFence", "definition", "reference", "resource" ]
12
+ );
10
13
 
11
14
  module.exports = {
12
15
  "names": [ "MD044", "proper-names" ],
@@ -22,30 +25,25 @@ module.exports = {
22
25
  const htmlElements = params.config.html_elements;
23
26
  const includeHtmlElements =
24
27
  (htmlElements === undefined) ? true : !!htmlElements;
25
- const exclusions = [];
26
- forEachLine(lineMetadata(), (line, lineIndex) => {
27
- if (linkReferenceDefinitionRe.test(line)) {
28
- exclusions.push([ lineIndex, 0, line.length ]);
29
- } else {
30
- let match = null;
31
- while ((match = bareUrlRe.exec(line)) !== null) {
32
- exclusions.push([ lineIndex, match.index, match[0].length ]);
33
- }
34
- forEachLink(line, (index, _, text, destination) => {
35
- if (destination) {
36
- exclusions.push(
37
- [ lineIndex, index + text.length, destination.length ]
38
- );
39
- }
40
- });
41
- }
42
- });
43
- if (!includeCodeBlocks) {
44
- exclusions.push(...codeBlockAndSpanRanges());
45
- }
46
- if (!includeHtmlElements) {
47
- exclusions.push(...htmlElementRanges());
28
+ const scannedTypes = new Set([ "data", "htmlFlowData" ]);
29
+ if (includeCodeBlocks) {
30
+ scannedTypes.add("codeFlowValue");
31
+ scannedTypes.add("codeTextData");
48
32
  }
33
+ const contentTokens =
34
+ filterByPredicate(
35
+ params.parsers.micromark.tokens,
36
+ (token) => scannedTypes.has(token.type),
37
+ (token) => {
38
+ let { children } = token;
39
+ if (!includeHtmlElements && (token.type === "htmlFlow")) {
40
+ children = children.slice(1, -1);
41
+ }
42
+ return children.filter((t) => !ignoredChildTypes.has(t.type));
43
+ }
44
+ );
45
+ const exclusions = [];
46
+ const autoLinked = new Set();
49
47
  for (const name of names) {
50
48
  const escapedName = escapeForRegExp(name);
51
49
  const startNamePattern = /^\W/.test(name) ? "" : "\\b_*";
@@ -53,36 +51,53 @@ module.exports = {
53
51
  const namePattern =
54
52
  `(${startNamePattern})(${escapedName})${endNamePattern}`;
55
53
  const nameRe = new RegExp(namePattern, "gi");
56
- forEachLine(lineMetadata(), (line, lineIndex, inCode, onFence) => {
57
- if (includeCodeBlocks || (!inCode && !onFence)) {
58
- let match = null;
59
- while ((match = nameRe.exec(line)) !== null) {
60
- const [ , leftMatch, nameMatch ] = match;
61
- const index = match.index + leftMatch.length;
62
- const length = nameMatch.length;
63
- if (
64
- !withinAnyRange(exclusions, lineIndex, index, length) &&
65
- !names.includes(nameMatch)
66
- ) {
54
+ for (const token of contentTokens) {
55
+ let match = null;
56
+ while ((match = nameRe.exec(token.text)) !== null) {
57
+ const [ , leftMatch, nameMatch ] = match;
58
+ const index = token.startColumn - 1 + match.index + leftMatch.length;
59
+ const length = nameMatch.length;
60
+ const lineIndex = token.startLine - 1;
61
+ if (
62
+ !withinAnyRange(exclusions, lineIndex, index, length) &&
63
+ !names.includes(nameMatch)
64
+ ) {
65
+ let urlRanges = [];
66
+ if (!autoLinked.has(token)) {
67
+ urlRanges = filterByTypes(
68
+ parse(token.text),
69
+ [ "literalAutolink" ]
70
+ ).map(
71
+ (t) => [
72
+ lineIndex,
73
+ token.startColumn - 1 + t.startColumn - 1,
74
+ t.endColumn - t.startColumn
75
+ ]
76
+ );
77
+ exclusions.push(...urlRanges);
78
+ autoLinked.add(token);
79
+ }
80
+ if (!withinAnyRange(urlRanges, lineIndex, index, length)) {
81
+ const column = index + 1;
67
82
  addErrorDetailIf(
68
83
  onError,
69
- lineIndex + 1,
84
+ token.startLine,
70
85
  name,
71
86
  nameMatch,
72
87
  null,
73
88
  null,
74
- [ index + 1, length ],
89
+ [ column, length ],
75
90
  {
76
- "editColumn": index + 1,
91
+ "editColumn": column,
77
92
  "deleteCount": length,
78
93
  "insertText": name
79
94
  }
80
95
  );
81
96
  }
82
- exclusions.push([ lineIndex, index, length ]);
83
97
  }
98
+ exclusions.push([ lineIndex, index, length ]);
84
99
  }
85
- });
100
+ }
86
101
  }
87
102
  }
88
103
  };