markdownlint 0.34.0 → 0.36.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 (90) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +97 -45
  3. package/demo/markdownlint-browser.js +2186 -2164
  4. package/doc/CustomRules.md +66 -255
  5. package/doc/Rules.md +102 -29
  6. package/doc/md003.md +2 -2
  7. package/doc/md004.md +4 -3
  8. package/doc/md034.md +5 -0
  9. package/doc/md035.md +3 -8
  10. package/doc/md044.md +10 -0
  11. package/doc/md046.md +2 -2
  12. package/doc/md048.md +3 -2
  13. package/doc/md049.md +4 -3
  14. package/doc/md050.md +4 -4
  15. package/doc/md051.md +11 -3
  16. package/doc/md055.md +3 -2
  17. package/doc/md058.md +48 -0
  18. package/helpers/README.md +2 -45
  19. package/helpers/helpers.js +115 -607
  20. package/helpers/micromark-helpers.cjs +296 -0
  21. package/helpers/micromark-parse.cjs +313 -0
  22. package/helpers/package.json +5 -2
  23. package/helpers/shared.js +6 -0
  24. package/helpers/test.cjs +28 -0
  25. package/lib/cache.js +62 -13
  26. package/lib/configuration-strict.d.ts +1199 -0
  27. package/lib/configuration.d.ts +3 -1173
  28. package/lib/constants.js +3 -2
  29. package/lib/markdownit.cjs +172 -0
  30. package/lib/markdownlint.d.ts +103 -56
  31. package/lib/markdownlint.js +180 -120
  32. package/lib/md001.js +15 -9
  33. package/lib/md003.js +14 -9
  34. package/lib/md004.js +55 -48
  35. package/lib/md005.js +4 -13
  36. package/lib/md007.js +7 -11
  37. package/lib/md009.js +50 -44
  38. package/lib/md010.js +52 -33
  39. package/lib/md011.js +32 -23
  40. package/lib/md012.js +17 -9
  41. package/lib/md013.js +44 -38
  42. package/lib/md014.js +31 -38
  43. package/lib/md018.js +17 -9
  44. package/lib/md019-md021.js +80 -0
  45. package/lib/md020.js +14 -8
  46. package/lib/md022.js +13 -29
  47. package/lib/md023.js +22 -23
  48. package/lib/md024.js +12 -9
  49. package/lib/md025.js +15 -10
  50. package/lib/md026.js +2 -7
  51. package/lib/md027.js +7 -12
  52. package/lib/md028.js +3 -8
  53. package/lib/md029.js +40 -34
  54. package/lib/md030.js +4 -10
  55. package/lib/md031.js +45 -23
  56. package/lib/md032.js +33 -31
  57. package/lib/md033.js +3 -8
  58. package/lib/md034.js +21 -34
  59. package/lib/md035.js +2 -7
  60. package/lib/md036.js +19 -24
  61. package/lib/md037.js +3 -8
  62. package/lib/md038.js +11 -22
  63. package/lib/md039.js +64 -54
  64. package/lib/md040.js +13 -20
  65. package/lib/md041.js +5 -5
  66. package/lib/md042.js +44 -38
  67. package/lib/md043.js +20 -14
  68. package/lib/md044.js +34 -36
  69. package/lib/md045.js +5 -10
  70. package/lib/md046.js +2 -8
  71. package/lib/md048.js +30 -23
  72. package/lib/md049-md050.js +22 -12
  73. package/lib/md051.js +20 -28
  74. package/lib/md052.js +2 -2
  75. package/lib/md053.js +7 -6
  76. package/lib/md054.js +16 -23
  77. package/lib/md055.js +41 -49
  78. package/lib/md056.js +32 -33
  79. package/lib/md058.js +58 -0
  80. package/lib/rules.js +11 -4
  81. package/package.json +23 -20
  82. package/schema/.markdownlint.jsonc +58 -52
  83. package/schema/.markdownlint.yaml +56 -51
  84. package/schema/ValidatingConfiguration.md +6 -8
  85. package/schema/markdownlint-config-schema-strict.json +1841 -0
  86. package/schema/markdownlint-config-schema.json +141 -109
  87. package/style/prettier.json +1 -0
  88. package/helpers/micromark.cjs +0 -445
  89. package/lib/md019.js +0 -42
  90. package/lib/md021.js +0 -65
package/lib/md039.js CHANGED
@@ -2,10 +2,52 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorContext, filterTokens } = require("../helpers");
5
+ const { addErrorContext } = require("../helpers");
6
+ const { getReferenceLinkImageData, filterByTypesCached } = require("./cache");
6
7
 
7
- const spaceInLinkRe =
8
- /\[(?:\s[^\]]*|[^\]]*?\s)\](?=(\([^)]*\)|\[[^\]]*\]))/;
8
+ /**
9
+ * Adds an error for a label space issue.
10
+ *
11
+ * @param {import("./markdownlint").RuleOnError} onError Error-reporting callback.
12
+ * @param {import("../helpers/micromark-helpers.cjs").Token} label Label token.
13
+ * @param {import("../helpers/micromark-helpers.cjs").Token} labelText LabelText token.
14
+ * @param {boolean} isStart True iff error is at the start of the link.
15
+ */
16
+ function addLabelSpaceError(onError, label, labelText, isStart) {
17
+ const match = labelText.text.match(isStart ? /^[^\S\r\n]+/ : /[^\S\r\n]+$/);
18
+ const range = match ?
19
+ [
20
+ (isStart ? (labelText.startColumn) : (labelText.endColumn - match[0].length)),
21
+ match[0].length
22
+ ] :
23
+ undefined;
24
+ addErrorContext(
25
+ onError,
26
+ isStart ? (labelText.startLine + (match ? 0 : 1)) : (labelText.endLine - (match ? 0 : 1)),
27
+ label.text.replace(/\s+/g, " "),
28
+ isStart,
29
+ !isStart,
30
+ range,
31
+ range ?
32
+ {
33
+ "editColumn": range[0],
34
+ "deleteCount": range[1]
35
+ } :
36
+ undefined
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Determines if a link is a valid link (and not a fake shortcut link due to parser tricks).
42
+ *
43
+ * @param {import("../helpers/micromark-helpers.cjs").Token} label Label token.
44
+ * @param {import("../helpers/micromark-helpers.cjs").Token} labelText LabelText token.
45
+ * @param {Map<string, any>} definitions Map of link definitions.
46
+ * @returns {boolean} True iff the link is valid.
47
+ */
48
+ function validLink(label, labelText, definitions) {
49
+ return (label.parent?.children.length !== 1) || definitions.has(labelText.text.trim());
50
+ }
9
51
 
10
52
  // eslint-disable-next-line jsdoc/valid-types
11
53
  /** @type import("./markdownlint").Rule */
@@ -13,59 +55,27 @@ module.exports = {
13
55
  "names": [ "MD039", "no-space-in-links" ],
14
56
  "description": "Spaces inside link text",
15
57
  "tags": [ "whitespace", "links" ],
16
- "parser": "markdownit",
58
+ "parser": "micromark",
17
59
  "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);
60
+ const { definitions } = getReferenceLinkImageData();
61
+ const labels = filterByTypesCached([ "label" ])
62
+ .filter((label) => label.parent?.type === "link");
63
+ for (const label of labels) {
64
+ const labelTexts = label.children.filter((child) => child.type === "labelText");
65
+ for (const labelText of labelTexts) {
66
+ if (
67
+ (labelText.text.trimStart().length !== labelText.text.length) &&
68
+ validLink(label, labelText, definitions)
69
+ ) {
70
+ addLabelSpaceError(onError, label, labelText, true);
71
+ }
72
+ if (
73
+ (labelText.text.trimEnd().length !== labelText.text.length) &&
74
+ validLink(label, labelText, definitions)
75
+ ) {
76
+ addLabelSpaceError(onError, label, labelText, false);
67
77
  }
68
78
  }
69
- });
79
+ }
70
80
  }
71
81
  };
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 { getDescendantsByType } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
8
8
 
9
9
  // eslint-disable-next-line jsdoc/valid-types
10
10
  /** @type import("./markdownlint").Rule */
@@ -17,25 +17,18 @@ 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
- const openingFence = tokenIfType(fencedCode.children[0], "codeFencedFence");
28
- if (openingFence) {
29
- const { children, startLine, text } = openingFence;
30
- const info = getTokenTextByType(children, "codeFencedFenceInfo");
31
- if (!info) {
32
- addErrorContext(onError, startLine, text);
33
- } else if ((allowed.length > 0) && !allowed.includes(info)) {
34
- addError(onError, startLine, `"${info}" is not allowed`);
35
- }
36
- if (languageOnly && getTokenTextByType(children, "codeFencedFenceMeta")) {
37
- addError(onError, startLine, `Info string contains more than language: "${text}"`);
38
- }
22
+ const openingFence = getDescendantsByType(fencedCode, [ "codeFencedFence" ])[0];
23
+ const { startLine, text } = openingFence;
24
+ const info = getDescendantsByType(openingFence, [ "codeFencedFenceInfo" ])[0]?.text;
25
+ if (!info) {
26
+ addErrorContext(onError, startLine, text);
27
+ } else if ((allowed.length > 0) && !allowed.includes(info)) {
28
+ addError(onError, startLine, `"${info}" is not allowed`);
29
+ }
30
+ if (languageOnly && getDescendantsByType(openingFence, [ "codeFencedFenceMeta" ]).length > 0) {
31
+ addError(onError, startLine, `Info string contains more than language: "${text}"`);
39
32
  }
40
33
  }
41
34
  }
package/lib/md041.js CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  const { addErrorContext, frontMatterHasTitle } = require("../helpers");
6
6
  const { filterByTypes, getHeadingLevel, getHtmlTagInfo, isHtmlFlowComment, nonContentTokens } =
7
- require("../helpers/micromark.cjs");
7
+ require("../helpers/micromark-helpers.cjs");
8
8
 
9
9
  // eslint-disable-next-line jsdoc/valid-types
10
10
  /** @type import("./markdownlint").Rule */
@@ -16,14 +16,14 @@ module.exports = {
16
16
  "function": function MD041(params, onError) {
17
17
  const level = Number(params.config.level || 1);
18
18
  if (!frontMatterHasTitle(params.frontMatterLines, params.config.front_matter_title)) {
19
- params.parsers.micromark.tokens.
20
- filter((token) => !nonContentTokens.has(token.type) && !isHtmlFlowComment(token)).
21
- every((token) => {
19
+ params.parsers.micromark.tokens
20
+ .filter((token) => !nonContentTokens.has(token.type) && !isHtmlFlowComment(token))
21
+ .every((token) => {
22
22
  let isError = true;
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-helpers.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,48 @@ 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 resourceDestinationString = getDescendantsByType(resource, [ "resourceDestination", [ "resourceDestinationLiteral", "resourceDestinationRaw" ], "resourceDestinationString" ]);
29
+ const hasLabelText = labelText.length > 0;
30
+ const hasReference = reference.length > 0;
31
+ const hasResource = resource.length > 0;
32
+ const hasReferenceString = referenceString.length > 0;
33
+ const hasResourceDestinationString = resourceDestinationString.length > 0;
34
+ let error = false;
35
+ if (
36
+ hasLabelText &&
37
+ ((!hasReference && !hasResource) || (hasReference && !hasReferenceString))
38
+ ) {
39
+ error = isReferenceDefinitionHash(labelText[0]);
40
+ } else if (hasReferenceString && !hasResourceDestinationString) {
41
+ error = isReferenceDefinitionHash(referenceString[0]);
42
+ } else if (!hasReferenceString && hasResourceDestinationString) {
43
+ error = (resourceDestinationString[0].text.trim() === "#");
44
+ } else if (!hasReferenceString && !hasResourceDestinationString) {
45
+ error = true;
50
46
  }
51
- });
47
+ if (error) {
48
+ addErrorContext(
49
+ onError,
50
+ link.startLine,
51
+ link.text,
52
+ undefined,
53
+ undefined,
54
+ [ link.startColumn, link.endColumn - link.startColumn ]
55
+ );
56
+ }
57
+ }
52
58
  }
53
59
  };
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-helpers.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
@@ -2,10 +2,9 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorDetailIf, escapeForRegExp, withinAnyRange } =
6
- require("../helpers");
7
- const { filterByPredicate, filterByTypes, parse } =
8
- require("../helpers/micromark.cjs");
5
+ const { addErrorDetailIf, escapeForRegExp, hasOverlap } = require("../helpers");
6
+ const { filterByPredicate, filterByTypes } = require("../helpers/micromark-helpers.cjs");
7
+ const { parse } = require("../helpers/micromark-parse.cjs");
9
8
 
10
9
  const ignoredChildTypes = new Set(
11
10
  [ "codeFencedFence", "definition", "reference", "resource" ]
@@ -32,11 +31,6 @@ module.exports = {
32
31
  const htmlElements = params.config.html_elements;
33
32
  const includeHtmlElements =
34
33
  (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
34
  const scannedTypes = new Set([ "data" ]);
41
35
  if (includeCodeBlocks) {
42
36
  scannedTypes.add("codeFlowValue");
@@ -48,56 +42,60 @@ module.exports = {
48
42
  }
49
43
  const contentTokens =
50
44
  filterByPredicate(
51
- micromarkTokens,
45
+ params.parsers.micromark.tokens,
52
46
  (token) => scannedTypes.has(token.type),
53
47
  (token) => (
54
48
  token.children.filter((t) => !ignoredChildTypes.has(t.type))
55
49
  )
56
50
  );
51
+ /** @type {import("../helpers").FileRange[]} */
57
52
  const exclusions = [];
58
- const autoLinked = new Set();
53
+ const scannedTokens = new Set();
59
54
  for (const name of names) {
60
55
  const escapedName = escapeForRegExp(name);
61
56
  const startNamePattern = /^\W/.test(name) ? "" : "\\b_*";
62
57
  const endNamePattern = /\W$/.test(name) ? "" : "_*\\b";
63
- const namePattern =
64
- `(${startNamePattern})(${escapedName})${endNamePattern}`;
58
+ const namePattern = `(${startNamePattern})(${escapedName})${endNamePattern}`;
65
59
  const nameRe = new RegExp(namePattern, "gi");
66
60
  for (const token of contentTokens) {
67
61
  let match = null;
68
62
  while ((match = nameRe.exec(token.text)) !== null) {
69
63
  const [ , leftMatch, nameMatch ] = match;
70
- const index = token.startColumn - 1 + match.index + leftMatch.length;
64
+ const column = token.startColumn + match.index + leftMatch.length;
71
65
  const length = nameMatch.length;
72
- const lineIndex = token.startLine - 1;
66
+ const lineNumber = token.startLine;
67
+ /** @type {import("../helpers").FileRange} */
68
+ const nameRange = {
69
+ "startLine": lineNumber,
70
+ "startColumn": column,
71
+ "endLine": lineNumber,
72
+ "endColumn": column + length - 1
73
+ };
73
74
  if (
74
- !withinAnyRange(exclusions, lineIndex, index, length) &&
75
- !names.includes(nameMatch)
75
+ !names.includes(nameMatch) &&
76
+ !exclusions.some((exclusion) => hasOverlap(exclusion, nameRange))
76
77
  ) {
77
- let urlRanges = [];
78
- if (!autoLinked.has(token)) {
79
- urlRanges = filterByTypes(
80
- parse(token.text),
81
- [ "literalAutolink" ]
82
- ).map(
83
- (t) => [
84
- lineIndex,
85
- token.startColumn - 1 + t.startColumn - 1,
86
- t.endColumn - t.startColumn
87
- ]
88
- );
89
- exclusions.push(...urlRanges);
90
- autoLinked.add(token);
78
+ /** @type {import("../helpers").FileRange[]} */
79
+ let autolinkRanges = [];
80
+ if (!scannedTokens.has(token)) {
81
+ autolinkRanges = filterByTypes(parse(token.text), [ "literalAutolink" ])
82
+ .map((tok) => ({
83
+ "startLine": lineNumber,
84
+ "startColumn": token.startColumn + tok.startColumn - 1,
85
+ "endLine": lineNumber,
86
+ "endColumn": token.endColumn + tok.endColumn - 1
87
+ }));
88
+ exclusions.push(...autolinkRanges);
89
+ scannedTokens.add(token);
91
90
  }
92
- if (!withinAnyRange(urlRanges, lineIndex, index, length)) {
93
- const column = index + 1;
91
+ if (!autolinkRanges.some((autolinkRange) => hasOverlap(autolinkRange, nameRange))) {
94
92
  addErrorDetailIf(
95
93
  onError,
96
94
  token.startLine,
97
95
  name,
98
96
  nameMatch,
99
- null,
100
- null,
97
+ undefined,
98
+ undefined,
101
99
  [ column, length ],
102
100
  {
103
101
  "editColumn": column,
@@ -107,7 +105,7 @@ module.exports = {
107
105
  );
108
106
  }
109
107
  }
110
- exclusions.push([ lineIndex, index, length ]);
108
+ exclusions.push(nameRange);
111
109
  }
112
110
  }
113
111
  }
package/lib/md045.js CHANGED
@@ -3,7 +3,8 @@
3
3
  "use strict";
4
4
 
5
5
  const { addError, getHtmlAttributeRe, nextLinesRe } = require("../helpers");
6
- const { filterByTypes, getHtmlTagInfo } = require("../helpers/micromark.cjs");
6
+ const { getHtmlTagInfo, getDescendantsByType } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
7
8
 
8
9
  const altRe = getHtmlAttributeRe("alt");
9
10
 
@@ -15,16 +16,10 @@ 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
- const labelTexts = filterByTypes(image.children, [ "labelText" ]);
22
+ const labelTexts = getDescendantsByType(image, [ "label", "labelText" ]);
28
23
  if (labelTexts.some((labelText) => labelText.text.length === 0)) {
29
24
  const range = (image.startLine === image.endLine) ?
30
25
  [ image.startColumn, image.endColumn - image.startColumn ] :
@@ -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);
package/lib/md046.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
  const tokenTypeToStyle = {
9
9
  "codeFenced": "fenced",
@@ -19,13 +19,7 @@ module.exports = {
19
19
  "parser": "micromark",
20
20
  "function": function MD046(params, onError) {
21
21
  let expectedStyle = String(params.config.style || "consistent");
22
- // eslint-disable-next-line jsdoc/valid-types
23
- /** @type import("../helpers/micromark.cjs").Token[] */
24
- const micromarkTokens =
25
- // @ts-ignore
26
- params.parsers.micromark.tokens;
27
- const codeBlocksAndFences = filterByTypes(micromarkTokens, [ "codeFenced", "codeIndented" ]);
28
- for (const token of codeBlocksAndFences) {
22
+ for (const token of filterByTypesCached([ "codeFenced", "codeIndented" ])) {
29
23
  const { startLine, type } = token;
30
24
  if (expectedStyle === "consistent") {
31
25
  expectedStyle = tokenTypeToStyle[type];
package/lib/md048.js CHANGED
@@ -2,8 +2,24 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addErrorDetailIf, fencedCodeBlockStyleFor } = require("../helpers");
6
- const { filterByTypes, tokenIfType } = require("../helpers/micromark.cjs");
5
+ const { addErrorDetailIf } = require("../helpers");
6
+ const { getDescendantsByType } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
8
+
9
+ /**
10
+ * Return the string representation of a fence markup character.
11
+ *
12
+ * @param {string} markup Fence string.
13
+ * @returns {"tilde" | "backtick"} String representation.
14
+ */
15
+ function fencedCodeBlockStyleFor(markup) {
16
+ switch (markup[0]) {
17
+ case "~":
18
+ return "tilde";
19
+ default:
20
+ return "backtick";
21
+ }
22
+ };
7
23
 
8
24
  // eslint-disable-next-line jsdoc/valid-types
9
25
  /** @type import("./markdownlint").Rule */
@@ -14,30 +30,21 @@ module.exports = {
14
30
  "parser": "micromark",
15
31
  "function": function MD048(params, onError) {
16
32
  const style = String(params.config.style || "consistent");
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
33
  let expectedStyle = style;
23
- const codeFenceds = filterByTypes(micromarkTokens, [ "codeFenced" ]);
34
+ const codeFenceds = filterByTypesCached([ "codeFenced" ]);
24
35
  for (const codeFenced of codeFenceds) {
25
- const codeFencedFence = tokenIfType(codeFenced.children[0], "codeFencedFence");
26
- if (codeFencedFence) {
27
- const codeFencedFenceSequence = tokenIfType(codeFencedFence.children[0], "codeFencedFenceSequence");
28
- if (codeFencedFenceSequence) {
29
- const { startLine, text } = codeFencedFenceSequence;
30
- if (expectedStyle === "consistent") {
31
- expectedStyle = fencedCodeBlockStyleFor(text);
32
- }
33
- addErrorDetailIf(
34
- onError,
35
- startLine,
36
- expectedStyle,
37
- fencedCodeBlockStyleFor(text)
38
- );
39
- }
36
+ const codeFencedFenceSequence =
37
+ getDescendantsByType(codeFenced, [ "codeFencedFence", "codeFencedFenceSequence" ])[0];
38
+ const { startLine, text } = codeFencedFenceSequence;
39
+ if (expectedStyle === "consistent") {
40
+ expectedStyle = fencedCodeBlockStyleFor(text);
40
41
  }
42
+ addErrorDetailIf(
43
+ onError,
44
+ startLine,
45
+ expectedStyle,
46
+ fencedCodeBlockStyleFor(text)
47
+ );
41
48
  }
42
49
  }
43
50
  };