markdownlint 0.34.0 → 0.35.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 (76) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +25 -10
  3. package/demo/markdownlint-browser.js +1144 -1166
  4. package/doc/CustomRules.md +11 -6
  5. package/doc/Rules.md +66 -0
  6. package/doc/md034.md +5 -0
  7. package/doc/md044.md +10 -0
  8. package/doc/md058.md +48 -0
  9. package/helpers/README.md +3 -29
  10. package/helpers/helpers.js +56 -288
  11. package/helpers/micromark.cjs +111 -12
  12. package/helpers/package.json +1 -1
  13. package/lib/cache.js +62 -13
  14. package/lib/configuration-strict.d.ts +1185 -0
  15. package/lib/configuration.d.ts +3 -1173
  16. package/lib/constants.js +3 -2
  17. package/lib/markdownlint.d.ts +65 -56
  18. package/lib/markdownlint.js +59 -37
  19. package/lib/md001.js +15 -9
  20. package/lib/md003.js +14 -9
  21. package/lib/md004.js +55 -48
  22. package/lib/md005.js +4 -13
  23. package/lib/md007.js +5 -9
  24. package/lib/md009.js +50 -44
  25. package/lib/md010.js +48 -33
  26. package/lib/md011.js +16 -8
  27. package/lib/md012.js +17 -9
  28. package/lib/md013.js +44 -38
  29. package/lib/md014.js +35 -38
  30. package/lib/md018.js +17 -9
  31. package/lib/md019-md021.js +80 -0
  32. package/lib/md020.js +13 -7
  33. package/lib/md022.js +6 -16
  34. package/lib/md023.js +22 -23
  35. package/lib/md024.js +12 -9
  36. package/lib/md025.js +15 -10
  37. package/lib/md026.js +2 -7
  38. package/lib/md027.js +5 -10
  39. package/lib/md028.js +3 -8
  40. package/lib/md029.js +40 -34
  41. package/lib/md030.js +4 -10
  42. package/lib/md031.js +43 -23
  43. package/lib/md032.js +16 -26
  44. package/lib/md033.js +3 -8
  45. package/lib/md034.js +5 -10
  46. package/lib/md035.js +2 -7
  47. package/lib/md036.js +3 -2
  48. package/lib/md037.js +2 -7
  49. package/lib/md038.js +3 -9
  50. package/lib/md039.js +66 -54
  51. package/lib/md040.js +3 -8
  52. package/lib/md041.js +1 -1
  53. package/lib/md042.js +48 -38
  54. package/lib/md043.js +20 -14
  55. package/lib/md044.js +3 -8
  56. package/lib/md045.js +3 -8
  57. package/lib/md046.js +2 -8
  58. package/lib/md048.js +3 -7
  59. package/lib/md049-md050.js +2 -7
  60. package/lib/md051.js +6 -12
  61. package/lib/md052.js +2 -2
  62. package/lib/md053.js +2 -2
  63. package/lib/md054.js +8 -13
  64. package/lib/md055.js +7 -8
  65. package/lib/md056.js +15 -11
  66. package/lib/md058.js +43 -0
  67. package/lib/rules.js +11 -4
  68. package/package.json +19 -17
  69. package/schema/.markdownlint.jsonc +54 -51
  70. package/schema/.markdownlint.yaml +53 -50
  71. package/schema/ValidatingConfiguration.md +6 -8
  72. package/schema/markdownlint-config-schema-strict.json +1819 -0
  73. package/schema/markdownlint-config-schema.json +115 -105
  74. package/style/prettier.json +1 -0
  75. package/lib/md019.js +0 -42
  76. package/lib/md021.js +0 -65
package/lib/md032.js CHANGED
@@ -2,28 +2,12 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorContext, blockquotePrefixRe, isBlankLine } = require("../helpers");
5
+ const { addErrorContextForLine, isBlankLine } = require("../helpers");
6
6
  const { filterByPredicate, nonContentTokens } = require("../helpers/micromark.cjs");
7
7
 
8
8
  const isList = (token) => (
9
9
  (token.type === "listOrdered") || (token.type === "listUnordered")
10
10
  );
11
- const addBlankLineError = (onError, lines, lineIndex, lineNumber) => {
12
- const line = lines[lineIndex];
13
- const quotePrefix = line.match(blockquotePrefixRe)[0].trimEnd();
14
- addErrorContext(
15
- onError,
16
- lineIndex + 1,
17
- line.trim(),
18
- null,
19
- null,
20
- null,
21
- {
22
- lineNumber,
23
- "insertText": `${quotePrefix}\n`
24
- }
25
- );
26
- };
27
11
 
28
12
  // eslint-disable-next-line jsdoc/valid-types
29
13
  /** @type import("./markdownlint").Rule */
@@ -33,16 +17,11 @@ module.exports = {
33
17
  "tags": [ "bullet", "ul", "ol", "blank_lines" ],
34
18
  "parser": "micromark",
35
19
  "function": function MD032(params, onError) {
36
- // eslint-disable-next-line jsdoc/valid-types
37
- /** @type import("../helpers/micromark.cjs").Token[] */
38
- const micromarkTokens =
39
- // @ts-ignore
40
- params.parsers.micromark.tokens;
41
- const { lines } = params;
20
+ const { lines, parsers } = params;
42
21
 
43
22
  // For every top-level list...
44
23
  const topLevelLists = filterByPredicate(
45
- micromarkTokens,
24
+ parsers.micromark.tokens,
46
25
  isList,
47
26
  (token) => (
48
27
  (isList(token) || (token.type === "htmlFlow")) ? [] : token.children
@@ -53,7 +32,12 @@ module.exports = {
53
32
  // Look for a blank line above the list
54
33
  const firstIndex = list.startLine - 1;
55
34
  if (!isBlankLine(lines[firstIndex - 1])) {
56
- addBlankLineError(onError, lines, firstIndex);
35
+ addErrorContextForLine(
36
+ onError,
37
+ // @ts-ignore
38
+ lines,
39
+ firstIndex
40
+ );
57
41
  }
58
42
 
59
43
  // Find the "visual" end of the list
@@ -69,7 +53,13 @@ module.exports = {
69
53
  // Look for a blank line below the list
70
54
  const lastIndex = endLine - 1;
71
55
  if (!isBlankLine(lines[lastIndex + 1])) {
72
- addBlankLineError(onError, lines, lastIndex, lastIndex + 2);
56
+ addErrorContextForLine(
57
+ onError,
58
+ // @ts-ignore
59
+ lines,
60
+ lastIndex,
61
+ lastIndex + 2
62
+ );
73
63
  }
74
64
  }
75
65
  }
package/lib/md033.js CHANGED
@@ -3,8 +3,8 @@
3
3
  "use strict";
4
4
 
5
5
  const { addError, nextLinesRe } = require("../helpers");
6
- const { filterByTypes, getHtmlTagInfo } =
7
- require("../helpers/micromark.cjs");
6
+ const { getHtmlTagInfo } = require("../helpers/micromark.cjs");
7
+ const { filterByTypesCached } = require("./cache");
8
8
 
9
9
  // eslint-disable-next-line jsdoc/valid-types
10
10
  /** @type import("./markdownlint").Rule */
@@ -17,12 +17,7 @@ module.exports = {
17
17
  let allowedElements = params.config.allowed_elements;
18
18
  allowedElements = Array.isArray(allowedElements) ? allowedElements : [];
19
19
  allowedElements = allowedElements.map((element) => element.toLowerCase());
20
- // eslint-disable-next-line jsdoc/valid-types
21
- /** @type import("../helpers/micromark.cjs").Token[] */
22
- const micromarkTokens =
23
- // @ts-ignore
24
- params.parsers.micromark.tokens;
25
- for (const token of filterByTypes(micromarkTokens, [ "htmlText" ])) {
20
+ for (const token of filterByTypesCached([ "htmlText" ], true)) {
26
21
  const htmlTagInfo = getHtmlTagInfo(token);
27
22
  if (
28
23
  htmlTagInfo &&
package/lib/md034.js CHANGED
@@ -3,8 +3,8 @@
3
3
  "use strict";
4
4
 
5
5
  const { addErrorContext } = require("../helpers");
6
- const { filterByPredicate, filterByTypes, getHtmlTagInfo, inHtmlFlow, parse } =
7
- require("../helpers/micromark.cjs");
6
+ const { filterByPredicate, getHtmlTagInfo, inHtmlFlow, parse } = require("../helpers/micromark.cjs");
7
+ const { filterByTypesCached } = require("./cache");
8
8
 
9
9
  // eslint-disable-next-line jsdoc/valid-types
10
10
  /** @type import("./markdownlint").Rule */
@@ -14,11 +14,6 @@ module.exports = {
14
14
  "tags": [ "links", "url" ],
15
15
  "parser": "micromark",
16
16
  "function": function MD034(params, onError) {
17
- // eslint-disable-next-line jsdoc/valid-types
18
- /** @type import("../helpers/micromark.cjs").Token[] */
19
- const micromarkTokens =
20
- // @ts-ignore
21
- params.parsers.micromark.tokens;
22
17
  const literalAutolinks = (tokens) => (
23
18
  filterByPredicate(
24
19
  tokens,
@@ -75,7 +70,7 @@ module.exports = {
75
70
  }
76
71
  )
77
72
  );
78
- const autoLinks = filterByTypes(micromarkTokens, [ "literalAutolink" ]);
73
+ const autoLinks = filterByTypesCached([ "literalAutolink" ]);
79
74
  if (autoLinks.length > 0) {
80
75
  // Re-parse with correct link/image reference definition handling
81
76
  const document = params.lines.join("\n");
@@ -94,8 +89,8 @@ module.exports = {
94
89
  onError,
95
90
  token.startLine,
96
91
  token.text,
97
- null,
98
- null,
92
+ undefined,
93
+ undefined,
99
94
  range,
100
95
  fixInfo
101
96
  );
package/lib/md035.js CHANGED
@@ -3,7 +3,7 @@
3
3
  "use strict";
4
4
 
5
5
  const { addErrorDetailIf } = require("../helpers");
6
- const { filterByTypes } = require("../helpers/micromark.cjs");
6
+ const { filterByTypesCached } = require("./cache");
7
7
 
8
8
  // eslint-disable-next-line jsdoc/valid-types
9
9
  /** @type import("./markdownlint").Rule */
@@ -14,12 +14,7 @@ module.exports = {
14
14
  "parser": "micromark",
15
15
  "function": function MD035(params, onError) {
16
16
  let style = String(params.config.style || "consistent").trim();
17
- // eslint-disable-next-line jsdoc/valid-types
18
- /** @type import("../helpers/micromark.cjs").Token[] */
19
- const micromarkTokens =
20
- // @ts-ignore
21
- params.parsers.micromark.tokens;
22
- const thematicBreaks = filterByTypes(micromarkTokens, [ "thematicBreak" ]);
17
+ const thematicBreaks = filterByTypesCached([ "thematicBreak" ]);
23
18
  for (const token of thematicBreaks) {
24
19
  const { startLine, text } = token;
25
20
  if (style === "consistent") {
package/lib/md036.js CHANGED
@@ -3,7 +3,8 @@
3
3
  "use strict";
4
4
 
5
5
  const { addErrorContext, allPunctuation } = require("../helpers");
6
- const { filterByTypes, matchAndGetTokensByType } = require("../helpers/micromark.cjs");
6
+ const { matchAndGetTokensByType } = require("../helpers/micromark.cjs");
7
+ const { filterByTypesCached } = require("./cache");
7
8
 
8
9
  /** @typedef {import("../helpers/micromark.cjs").TokenType} TokenType */
9
10
  /** @type {Map<TokenType, TokenType[]>} */
@@ -24,7 +25,7 @@ module.exports = {
24
25
  punctuation = String((punctuation === undefined) ? allPunctuation : punctuation);
25
26
  const punctuationRe = new RegExp("[" + punctuation + "]$");
26
27
  const paragraphTokens =
27
- filterByTypes(params.parsers.micromark.tokens, [ "paragraph" ]).
28
+ filterByTypesCached([ "paragraph" ]).
28
29
  filter((token) =>
29
30
  (token.parent?.type === "content") && !token.parent?.parent && (token.children.length === 1)
30
31
  );
package/lib/md037.js CHANGED
@@ -15,18 +15,13 @@ module.exports = {
15
15
  "function": function MD037(params, onError) {
16
16
 
17
17
  // Initialize variables
18
- // eslint-disable-next-line jsdoc/valid-types
19
- /** @type import("../helpers/micromark.cjs").Token[] */
20
- const micromarkTokens =
21
- // @ts-ignore
22
- params.parsers.micromark.tokens;
23
- const { lines } = params;
18
+ const { lines, parsers } = params;
24
19
  const emphasisTokensByMarker = new Map();
25
20
  for (const marker of [ "_", "__", "___", "*", "**", "***" ]) {
26
21
  emphasisTokensByMarker.set(marker, []);
27
22
  }
28
23
  const tokens = filterByPredicate(
29
- micromarkTokens,
24
+ parsers.micromark.tokens,
30
25
  (token) => token.children.some((child) => child.type === "data")
31
26
  );
32
27
  for (const token of tokens) {
package/lib/md038.js CHANGED
@@ -3,8 +3,8 @@
3
3
  "use strict";
4
4
 
5
5
  const { addErrorContext } = require("../helpers");
6
- const { filterByTypes, inHtmlFlow, tokenIfType } =
7
- require("../helpers/micromark.cjs");
6
+ const { tokenIfType } = require("../helpers/micromark.cjs");
7
+ const { filterByTypesCached } = require("./cache");
8
8
 
9
9
  const leftSpaceRe = /^\s(?:[^`]|$)/;
10
10
  const rightSpaceRe = /[^`]\s$/;
@@ -27,13 +27,7 @@ module.exports = {
27
27
  "tags": [ "whitespace", "code" ],
28
28
  "parser": "micromark",
29
29
  "function": function MD038(params, onError) {
30
- // eslint-disable-next-line jsdoc/valid-types
31
- /** @type import("../helpers/micromark.cjs").Token[] */
32
- const micromarkTokens =
33
- // @ts-ignore
34
- params.parsers.micromark.tokens;
35
- const codeTexts = filterByTypes(micromarkTokens, [ "codeText" ])
36
- .filter((codeText) => !inHtmlFlow(codeText));
30
+ const codeTexts = filterByTypesCached([ "codeText" ]);
37
31
  for (const codeText of codeTexts) {
38
32
  const { children } = codeText;
39
33
  const first = 0;
package/lib/md039.js CHANGED
@@ -2,10 +2,51 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorContext, filterTokens } = require("../helpers");
5
+ const { addErrorContext } = require("../helpers");
6
+ const { filterByTypes } = require("../helpers/micromark.cjs");
7
+ const { getReferenceLinkImageData, filterByTypesCached } = require("./cache");
6
8
 
7
- const spaceInLinkRe =
8
- /\[(?:\s[^\]]*|[^\]]*?\s)\](?=(\([^)]*\)|\[[^\]]*\]))/;
9
+ /**
10
+ * Adds an error for a label space issue.
11
+ *
12
+ * @param {import("./markdownlint").RuleOnError} onError Error-reporting callback.
13
+ * @param {import("../helpers/micromark.cjs").Token} label Label token.
14
+ * @param {import("../helpers/micromark.cjs").Token} labelText LabelText token.
15
+ * @param {boolean} isStart True iff error is at the start of the link.
16
+ */
17
+ function addLabelSpaceError(onError, label, labelText, isStart) {
18
+ const match = labelText.text.match(isStart ? /^[^\S\r\n]+/ : /[^\S\r\n]+$/);
19
+ const range = match ?
20
+ [
21
+ (isStart ? (labelText.startColumn) : (labelText.endColumn - match[0].length)),
22
+ match[0].length
23
+ ] :
24
+ undefined;
25
+ addErrorContext(
26
+ onError,
27
+ isStart ? (labelText.startLine + (match ? 0 : 1)) : (labelText.endLine - (match ? 0 : 1)),
28
+ label.text.replace(/\s+/g, " "),
29
+ isStart,
30
+ !isStart,
31
+ range,
32
+ range ? {
33
+ "editColumn": range[0],
34
+ "deleteCount": range[1]
35
+ } : undefined
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Determines if a link is a valid link (and not a fake shortcut link due to parser tricks).
41
+ *
42
+ * @param {import("../helpers/micromark.cjs").Token} label Label token.
43
+ * @param {import("../helpers/micromark.cjs").Token} labelText LabelText token.
44
+ * @param {Map<string, any>} definitions Map of link definitions.
45
+ * @returns {boolean} True iff the link is valid.
46
+ */
47
+ function validLink(label, labelText, definitions) {
48
+ return (label.parent?.children.length !== 1) || definitions.has(labelText.text.trim());
49
+ }
9
50
 
10
51
  // eslint-disable-next-line jsdoc/valid-types
11
52
  /** @type import("./markdownlint").Rule */
@@ -13,59 +54,30 @@ module.exports = {
13
54
  "names": [ "MD039", "no-space-in-links" ],
14
55
  "description": "Spaces inside link text",
15
56
  "tags": [ "whitespace", "links" ],
16
- "parser": "markdownit",
57
+ "parser": "micromark",
17
58
  "function": function MD039(params, onError) {
18
- filterTokens(params, "inline", (token) => {
19
- const { children } = token;
20
- let { lineNumber } = token;
21
- let inLink = false;
22
- let linkText = "";
23
- let lineIndex = 0;
24
- for (const child of children) {
25
- const { content, markup, type } = child;
26
- if (type === "link_open") {
27
- inLink = true;
28
- linkText = "";
29
- } else if (type === "link_close") {
30
- inLink = false;
31
- const left = linkText.trimStart().length !== linkText.length;
32
- const right = linkText.trimEnd().length !== linkText.length;
33
- if (left || right) {
34
- const line = params.lines[lineNumber - 1];
35
- let range = null;
36
- let fixInfo = null;
37
- const match = line.slice(lineIndex).match(spaceInLinkRe);
38
- if (match) {
39
- // @ts-ignore
40
- const column = match.index + lineIndex + 1;
41
- const length = match[0].length;
42
- range = [ column, length ];
43
- fixInfo = {
44
- "editColumn": column + 1,
45
- "deleteCount": length - 2,
46
- "insertText": linkText.trim()
47
- };
48
- lineIndex = column + length - 1;
49
- }
50
- addErrorContext(
51
- onError,
52
- lineNumber,
53
- `[${linkText}]`,
54
- left,
55
- right,
56
- range,
57
- fixInfo
58
- );
59
- }
60
- } else if ((type === "softbreak") || (type === "hardbreak")) {
61
- lineNumber++;
62
- lineIndex = 0;
63
- } else if (inLink) {
64
- linkText += type.endsWith("_inline") ?
65
- `${markup}${content}${markup}` :
66
- (content || markup);
59
+ const { definitions } = getReferenceLinkImageData();
60
+ const labels = filterByTypesCached([ "label" ]).
61
+ filter((label) => label.parent?.type === "link");
62
+ for (const label of labels) {
63
+ const labelTexts = filterByTypes(
64
+ label.children,
65
+ [ "labelText" ]
66
+ );
67
+ for (const labelText of labelTexts) {
68
+ if (
69
+ (labelText.text.trimStart().length !== labelText.text.length) &&
70
+ validLink(label, labelText, definitions)
71
+ ) {
72
+ addLabelSpaceError(onError, label, labelText, true);
73
+ }
74
+ if (
75
+ (labelText.text.trimEnd().length !== labelText.text.length) &&
76
+ validLink(label, labelText, definitions)
77
+ ) {
78
+ addLabelSpaceError(onError, label, labelText, false);
67
79
  }
68
80
  }
69
- });
81
+ }
70
82
  }
71
83
  };
package/lib/md040.js CHANGED
@@ -3,8 +3,8 @@
3
3
  "use strict";
4
4
 
5
5
  const { addError, addErrorContext } = require("../helpers");
6
- const { filterByTypes, getTokenTextByType, tokenIfType } =
7
- require("../helpers/micromark.cjs");
6
+ const { getTokenTextByType, tokenIfType } = require("../helpers/micromark.cjs");
7
+ const { filterByTypesCached } = require("./cache");
8
8
 
9
9
  // eslint-disable-next-line jsdoc/valid-types
10
10
  /** @type import("./markdownlint").Rule */
@@ -17,12 +17,7 @@ module.exports = {
17
17
  let allowed = params.config.allowed_languages;
18
18
  allowed = Array.isArray(allowed) ? allowed : [];
19
19
  const languageOnly = !!params.config.language_only;
20
- // eslint-disable-next-line jsdoc/valid-types
21
- /** @type import("../helpers/micromark.cjs").Token[] */
22
- const micromarkTokens =
23
- // @ts-ignore
24
- params.parsers.micromark.tokens;
25
- const fencedCodes = filterByTypes(micromarkTokens, [ "codeFenced" ]);
20
+ const fencedCodes = filterByTypesCached([ "codeFenced" ]);
26
21
  for (const fencedCode of fencedCodes) {
27
22
  const openingFence = tokenIfType(fencedCode.children[0], "codeFencedFence");
28
23
  if (openingFence) {
package/lib/md041.js CHANGED
@@ -23,7 +23,7 @@ module.exports = {
23
23
  if ((token.type === "atxHeading") || (token.type === "setextHeading")) {
24
24
  isError = (getHeadingLevel(token) !== level);
25
25
  } else if (token.type === "htmlFlow") {
26
- const htmlTexts = filterByTypes(token.children, [ "htmlText" ]);
26
+ const htmlTexts = filterByTypes(token.children, [ "htmlText" ], true);
27
27
  const tagInfo = (htmlTexts.length > 0) && getHtmlTagInfo(htmlTexts[0]);
28
28
  isError = !tagInfo || (tagInfo.name.toLowerCase() !== `h${level}`);
29
29
  }
package/lib/md042.js CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorContext, escapeForRegExp, filterTokens } =
6
- require("../helpers");
5
+ const { addErrorContext } = require("../helpers");
6
+ const { getDescendantsByType } = require("../helpers/micromark.cjs");
7
+ const { getReferenceLinkImageData, filterByTypesCached } = require("./cache");
7
8
 
8
9
  // eslint-disable-next-line jsdoc/valid-types
9
10
  /** @type import("./markdownlint").Rule */
@@ -11,43 +12,52 @@ module.exports = {
11
12
  "names": [ "MD042", "no-empty-links" ],
12
13
  "description": "No empty links",
13
14
  "tags": [ "links" ],
14
- "parser": "markdownit",
15
+ "parser": "micromark",
15
16
  "function": function MD042(params, onError) {
16
- filterTokens(params, "inline", function forToken(token) {
17
- let inLink = false;
18
- let linkText = "";
19
- let emptyLink = false;
20
- for (const child of token.children) {
21
- if (child.type === "link_open") {
22
- inLink = true;
23
- linkText = "";
24
- for (const attr of child.attrs) {
25
- if (attr[0] === "href" && (!attr[1] || (attr[1] === "#"))) {
26
- emptyLink = true;
27
- }
28
- }
29
- } else if (child.type === "link_close") {
30
- inLink = false;
31
- if (emptyLink) {
32
- let context = `[${linkText}]`;
33
- let range = null;
34
- const match = child.line.match(
35
- new RegExp(`${escapeForRegExp(context)}\\((?:|#|<>)\\)`)
36
- );
37
- if (match) {
38
- context = match[0];
39
- // @ts-ignore
40
- range = [ match.index + 1, match[0].length ];
41
- }
42
- addErrorContext(
43
- onError, child.lineNumber, context, null, null, range
44
- );
45
- emptyLink = false;
46
- }
47
- } else if (inLink) {
48
- linkText += child.content;
49
- }
17
+ const { definitions } = getReferenceLinkImageData();
18
+ const isReferenceDefinitionHash = (token) => {
19
+ const definition = definitions.get(token.text.trim());
20
+ return (definition && (definition[1] === "#"));
21
+ };
22
+ const links = filterByTypesCached([ "link" ]);
23
+ for (const link of links) {
24
+ const labelText = getDescendantsByType(link, [ "label", "labelText" ]);
25
+ const reference = getDescendantsByType(link, [ "reference" ]);
26
+ const resource = getDescendantsByType(link, [ "resource" ]);
27
+ const referenceString = getDescendantsByType(reference, [ "referenceString" ]);
28
+ const resourceDestination = getDescendantsByType(resource, [ "resourceDestination" ]);
29
+ const resourceDestinationString = [
30
+ ...getDescendantsByType(resourceDestination, [ "resourceDestinationRaw", "resourceDestinationString" ]),
31
+ ...getDescendantsByType(resourceDestination, [ "resourceDestinationLiteral", "resourceDestinationString" ])
32
+ ];
33
+ const hasLabelText = labelText.length > 0;
34
+ const hasReference = reference.length > 0;
35
+ const hasResource = resource.length > 0;
36
+ const hasReferenceString = referenceString.length > 0;
37
+ const hasResourceDestinationString = resourceDestinationString.length > 0;
38
+ let error = false;
39
+ if (
40
+ hasLabelText &&
41
+ ((!hasReference && !hasResource) || (hasReference && !hasReferenceString))
42
+ ) {
43
+ error = isReferenceDefinitionHash(labelText[0]);
44
+ } else if (hasReferenceString && !hasResourceDestinationString) {
45
+ error = isReferenceDefinitionHash(referenceString[0]);
46
+ } else if (!hasReferenceString && hasResourceDestinationString) {
47
+ error = (resourceDestinationString[0].text.trim() === "#");
48
+ } else if (!hasReferenceString && !hasResourceDestinationString) {
49
+ error = true;
50
50
  }
51
- });
51
+ if (error) {
52
+ addErrorContext(
53
+ onError,
54
+ link.startLine,
55
+ link.text,
56
+ undefined,
57
+ undefined,
58
+ [ link.startColumn, link.endColumn - link.startColumn ]
59
+ );
60
+ }
61
+ }
52
62
  }
53
63
  };
package/lib/md043.js CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorContext, addErrorDetailIf, forEachHeading } =
6
- require("../helpers");
5
+ const { addErrorContext, addErrorDetailIf } = require("../helpers");
6
+ const { getHeadingLevel, getHeadingText } = require("../helpers/micromark.cjs");
7
+ const { filterByTypesCached } = require("./cache");
7
8
 
8
9
  // eslint-disable-next-line jsdoc/valid-types
9
10
  /** @type import("./markdownlint").Rule */
@@ -11,7 +12,7 @@ module.exports = {
11
12
  "names": [ "MD043", "required-headings" ],
12
13
  "description": "Required heading structure",
13
14
  "tags": [ "headings" ],
14
- "parser": "markdownit",
15
+ "parser": "micromark",
15
16
  "function": function MD043(params, onError) {
16
17
  const requiredHeadings = params.config.headings;
17
18
  if (!Array.isArray(requiredHeadings)) {
@@ -19,20 +20,18 @@ module.exports = {
19
20
  return;
20
21
  }
21
22
  const matchCase = params.config.match_case || false;
22
- const levels = {};
23
- for (const level of [ 1, 2, 3, 4, 5, 6 ]) {
24
- levels["h" + level] = "######".substr(-level);
25
- }
26
23
  let i = 0;
27
24
  let matchAny = false;
28
25
  let hasError = false;
29
26
  let anyHeadings = false;
30
27
  const getExpected = () => requiredHeadings[i++] || "[None]";
31
28
  const handleCase = (str) => (matchCase ? str : str.toLowerCase());
32
- forEachHeading(params, (heading, content) => {
29
+ for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
33
30
  if (!hasError) {
31
+ const headingText = getHeadingText(heading);
32
+ const headingLevel = getHeadingLevel(heading);
34
33
  anyHeadings = true;
35
- const actual = levels[heading.tag] + " " + content;
34
+ const actual = `${"".padEnd(headingLevel, "#")} ${headingText}`;
36
35
  const expected = getExpected();
37
36
  if (expected === "*") {
38
37
  const nextExpected = getExpected();
@@ -47,12 +46,16 @@ module.exports = {
47
46
  } else if (matchAny) {
48
47
  i--;
49
48
  } else {
50
- addErrorDetailIf(onError, heading.lineNumber,
51
- expected, actual);
49
+ addErrorDetailIf(
50
+ onError,
51
+ heading.startLine,
52
+ expected,
53
+ actual
54
+ );
52
55
  hasError = true;
53
56
  }
54
57
  }
55
- });
58
+ }
56
59
  const extraHeadings = requiredHeadings.length - i;
57
60
  if (
58
61
  !hasError &&
@@ -60,8 +63,11 @@ module.exports = {
60
63
  ((extraHeadings === 1) && (requiredHeadings[i] !== "*"))) &&
61
64
  (anyHeadings || !requiredHeadings.every((heading) => heading === "*"))
62
65
  ) {
63
- addErrorContext(onError, params.lines.length,
64
- requiredHeadings[i]);
66
+ addErrorContext(
67
+ onError,
68
+ params.lines.length,
69
+ requiredHeadings[i]
70
+ );
65
71
  }
66
72
  }
67
73
  };
package/lib/md044.js CHANGED
@@ -32,11 +32,6 @@ module.exports = {
32
32
  const htmlElements = params.config.html_elements;
33
33
  const includeHtmlElements =
34
34
  (htmlElements === undefined) ? true : !!htmlElements;
35
- // eslint-disable-next-line jsdoc/valid-types
36
- /** @type import("../helpers/micromark.cjs").Token[] */
37
- const micromarkTokens =
38
- // @ts-ignore
39
- params.parsers.micromark.tokens;
40
35
  const scannedTypes = new Set([ "data" ]);
41
36
  if (includeCodeBlocks) {
42
37
  scannedTypes.add("codeFlowValue");
@@ -48,7 +43,7 @@ module.exports = {
48
43
  }
49
44
  const contentTokens =
50
45
  filterByPredicate(
51
- micromarkTokens,
46
+ params.parsers.micromark.tokens,
52
47
  (token) => scannedTypes.has(token.type),
53
48
  (token) => (
54
49
  token.children.filter((t) => !ignoredChildTypes.has(t.type))
@@ -96,8 +91,8 @@ module.exports = {
96
91
  token.startLine,
97
92
  name,
98
93
  nameMatch,
99
- null,
100
- null,
94
+ undefined,
95
+ undefined,
101
96
  [ column, length ],
102
97
  {
103
98
  "editColumn": column,
package/lib/md045.js CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  const { addError, getHtmlAttributeRe, nextLinesRe } = require("../helpers");
6
6
  const { filterByTypes, getHtmlTagInfo } = require("../helpers/micromark.cjs");
7
+ const { filterByTypesCached } = require("./cache");
7
8
 
8
9
  const altRe = getHtmlAttributeRe("alt");
9
10
 
@@ -15,14 +16,8 @@ module.exports = {
15
16
  "tags": [ "accessibility", "images" ],
16
17
  "parser": "micromark",
17
18
  "function": function MD045(params, onError) {
18
- // eslint-disable-next-line jsdoc/valid-types
19
- /** @type import("../helpers/micromark.cjs").Token[] */
20
- const micromarkTokens =
21
- // @ts-ignore
22
- params.parsers.micromark.tokens;
23
-
24
19
  // Process Markdown images
25
- const images = filterByTypes(micromarkTokens, [ "image" ]);
20
+ const images = filterByTypesCached([ "image" ]);
26
21
  for (const image of images) {
27
22
  const labelTexts = filterByTypes(image.children, [ "labelText" ]);
28
23
  if (labelTexts.some((labelText) => labelText.text.length === 0)) {
@@ -40,7 +35,7 @@ module.exports = {
40
35
  }
41
36
 
42
37
  // Process HTML images
43
- const htmlTexts = filterByTypes(micromarkTokens, [ "htmlText" ]);
38
+ const htmlTexts = filterByTypesCached([ "htmlText" ], true);
44
39
  for (const htmlText of htmlTexts) {
45
40
  const { startColumn, startLine, text } = htmlText;
46
41
  const htmlTagInfo = getHtmlTagInfo(htmlText);