markdownlint 0.41.0 → 0.42.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 (66) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/CONTRIBUTING.md +62 -77
  3. package/README.md +7 -7
  4. package/doc/CustomRules.md +6 -4
  5. package/doc/Rules.md +87 -36
  6. package/doc/md010.md +0 -1
  7. package/doc/md024.md +1 -1
  8. package/doc/md029.md +11 -1
  9. package/doc/md043.md +47 -9
  10. package/doc/md051.md +19 -19
  11. package/doc/md055.md +6 -3
  12. package/doc/md060.md +3 -2
  13. package/helpers/README.md +2 -2
  14. package/helpers/helpers.cjs +23 -7
  15. package/helpers/micromark-helpers.cjs +8 -12
  16. package/helpers/package.json +1 -5
  17. package/helpers/shared.cjs +1 -1
  18. package/lib/cache.mjs +1 -0
  19. package/lib/configuration-strict.d.ts +124 -112
  20. package/lib/constants.mjs +1 -1
  21. package/lib/defer-require.cjs +1 -0
  22. package/lib/exports.d.mts +29 -1
  23. package/lib/markdownit.cjs +8 -1
  24. package/lib/markdownlint.d.mts +337 -195
  25. package/lib/markdownlint.mjs +83 -87
  26. package/lib/md004.mjs +1 -0
  27. package/lib/md005.mjs +1 -1
  28. package/lib/md007.mjs +2 -2
  29. package/lib/md009.mjs +2 -3
  30. package/lib/md010.mjs +1 -1
  31. package/lib/md011.mjs +10 -7
  32. package/lib/md012.mjs +1 -1
  33. package/lib/md013.mjs +13 -9
  34. package/lib/md018.mjs +1 -1
  35. package/lib/md019-md021.mjs +1 -0
  36. package/lib/md020.mjs +1 -1
  37. package/lib/md022.mjs +6 -6
  38. package/lib/md024.mjs +1 -1
  39. package/lib/md025.mjs +1 -1
  40. package/lib/md027.mjs +1 -1
  41. package/lib/md028.mjs +1 -2
  42. package/lib/md029.mjs +34 -23
  43. package/lib/md030.mjs +4 -4
  44. package/lib/md031.mjs +1 -1
  45. package/lib/md032.mjs +5 -4
  46. package/lib/md033.mjs +1 -1
  47. package/lib/md034.mjs +1 -1
  48. package/lib/md037.mjs +1 -2
  49. package/lib/md039.mjs +5 -1
  50. package/lib/md041.mjs +1 -1
  51. package/lib/md043.mjs +98 -44
  52. package/lib/md044.mjs +2 -2
  53. package/lib/md048.mjs +1 -3
  54. package/lib/md051.mjs +3 -3
  55. package/lib/md055.mjs +11 -1
  56. package/lib/md056.mjs +1 -1
  57. package/lib/md058.mjs +1 -1
  58. package/lib/md060.mjs +24 -4
  59. package/lib/micromark-parse.mjs +3 -2
  60. package/lib/resolve-module.cjs +3 -2
  61. package/lib/resolve-module.d.cts +15 -10
  62. package/package.json +33 -34
  63. package/schema/.markdownlint.jsonc +54 -54
  64. package/schema/.markdownlint.yaml +76 -76
  65. package/schema/markdownlint-config-schema-strict.json +126 -118
  66. package/schema/markdownlint-config-schema.json +126 -118
package/lib/md022.mjs CHANGED
@@ -11,14 +11,15 @@ const defaultLines = 1;
11
11
  // eslint-disable-next-line jsdoc/reject-any-type
12
12
  const getLinesFunction = (/** @type {any} */ linesParam) => {
13
13
  if (Array.isArray(linesParam)) {
14
+ // eslint-disable-next-line unicorn/no-unreadable-new-expression
14
15
  const linesArray = new Array(6).fill(defaultLines);
15
- for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) {
16
+ for (const [ index, value ] of linesParam.slice(0, 6).entries()) {
16
17
  linesArray[index] = value;
17
18
  }
18
19
  return (/** @type {MicromarkToken} */ heading) => linesArray[getHeadingLevel(heading) - 1];
19
20
  }
20
21
  // Coerce linesParam to a number
21
- const lines = (linesParam === undefined) ? defaultLines : Number(linesParam);
22
+ const lines = Number(linesParam ?? defaultLines);
22
23
  return () => lines;
23
24
  };
24
25
 
@@ -26,10 +27,9 @@ const getLine = (/** @type {readonly string[]} */ lines, /** @type {number} */ i
26
27
  if ((index >= 0) && (index < lines.length)) {
27
28
  return lines[index];
28
29
  }
29
- if (includeFrontMatter && (frontMatterLines.length > 0) && (index < 0) && (index >= -frontMatterLines.length)) {
30
- return frontMatterLines[frontMatterLines.length + index];
31
- }
32
- return "";
30
+ return (includeFrontMatter && (frontMatterLines.length > 0) && (index < 0) && (index >= -frontMatterLines.length)) ?
31
+ frontMatterLines[frontMatterLines.length + index] :
32
+ "";
33
33
  };
34
34
 
35
35
  /** @type {import("markdownlint").Rule} */
package/lib/md024.mjs CHANGED
@@ -11,7 +11,7 @@ export default {
11
11
  "tags": [ "headings" ],
12
12
  "parser": "micromark",
13
13
  "function": function MD024(params, onError) {
14
- const siblingsOnly = !!params.config.siblings_only || false;
14
+ const siblingsOnly = !!params.config.siblings_only;
15
15
  const knownContents = [ null, [] ];
16
16
  let lastLevel = 1;
17
17
  let knownContent = knownContents[lastLevel];
package/lib/md025.mjs CHANGED
@@ -11,7 +11,7 @@ export default {
11
11
  "tags": [ "headings" ],
12
12
  "parser": "micromark",
13
13
  "function": function MD025(params, onError) {
14
- const level = Number(params.config.level || 1);
14
+ const level = Number(params.config.level ?? 1);
15
15
  const { tokens } = params.parsers.micromark;
16
16
  const matchingHeadings = filterByTypesCached([ "atxHeading", "setextHeading" ])
17
17
  .filter((heading) => (level === getHeadingLevel(heading)) && !isDocfxTab(heading));
package/lib/md027.mjs CHANGED
@@ -15,7 +15,7 @@ export default {
15
15
  "parser": "micromark",
16
16
  "function": function MD027(params, onError) {
17
17
  const listItems = params.config.list_items;
18
- const includeListItems = (listItems === undefined) ? true : !!listItems;
18
+ const includeListItems = (listItems === undefined) || !!listItems;
19
19
  const { tokens } = params.parsers.micromark;
20
20
  for (const token of filterByTypesCached([ "linePrefix" ])) {
21
21
  const parent = token.parent;
package/lib/md028.mjs CHANGED
@@ -16,8 +16,7 @@ export default {
16
16
  const errorLineNumbers = [];
17
17
  const siblings = token.parent?.children || params.parsers.micromark.tokens;
18
18
  for (let i = siblings.indexOf(token) + 1; i < siblings.length; i++) {
19
- const sibling = siblings[i];
20
- const { startLine, type } = sibling;
19
+ const { startLine, type } = siblings[i];
21
20
  if (type === "lineEndingBlank") {
22
21
  // Possible blank between blockquotes
23
22
  errorLineNumbers.push(startLine);
package/lib/md029.mjs CHANGED
@@ -15,13 +15,15 @@ const listStyles = Object.keys(listStyleExamples);
15
15
  * Gets the column and text of an ordered list item prefix token.
16
16
  *
17
17
  * @param {import("markdownlint").MicromarkToken} listItemPrefix List item prefix token.
18
- * @returns {{column: number, value: number}} List item value column and text.
18
+ * @returns {{startColumn: number, endColumn: number, text: string, number: number}} List item value column and text.
19
19
  */
20
20
  function getOrderedListItemValue(listItemPrefix) {
21
- const listItemValue = getDescendantsByType(listItemPrefix, [ "listItemValue" ])[0];
21
+ const { startColumn, endColumn, text } = getDescendantsByType(listItemPrefix, [ "listItemValue" ])[0];
22
22
  return {
23
- "column": listItemValue.startColumn,
24
- "value": Number(listItemValue.text)
23
+ startColumn,
24
+ endColumn,
25
+ text,
26
+ "number": Number(text)
25
27
  };
26
28
  }
27
29
 
@@ -35,40 +37,49 @@ export default {
35
37
  const style = String(params.config.style);
36
38
  for (const listOrdered of filterByTypesCached([ "listOrdered" ])) {
37
39
  const listItemPrefixes = getDescendantsByType(listOrdered, [ "listItemPrefix" ]);
38
- let expected = 1;
40
+ let expectedNumber = 1;
39
41
  let incrementing = false;
40
- // Check for incrementing number pattern 1/2/3 or 0/1/2
41
- if (listItemPrefixes.length >= 2) {
42
- const first = getOrderedListItemValue(listItemPrefixes[0]);
43
- const second = getOrderedListItemValue(listItemPrefixes[1]);
44
- if ((second.value !== 1) || (first.value === 0)) {
45
- incrementing = true;
46
- if (first.value === 0) {
47
- expected = 0;
42
+ let rightAligned = false;
43
+ let zeroPrefixed = false;
44
+ if (listItemPrefixes.length > 0) {
45
+ // Check for right alignment
46
+ const firstPrefixEndColumn = listItemPrefixes[0].endColumn;
47
+ rightAligned = listItemPrefixes.every((prefix) => prefix.endColumn === firstPrefixEndColumn);
48
+ if (listItemPrefixes.length > 1) {
49
+ // Check for incrementing number pattern 1/2/3 or 0/1/2
50
+ const firstValue = getOrderedListItemValue(listItemPrefixes[0]);
51
+ const secondValue = getOrderedListItemValue(listItemPrefixes[1]);
52
+ if ((secondValue.number !== 1) || (firstValue.number === 0)) {
53
+ incrementing = true;
54
+ if (firstValue.number === 0) {
55
+ expectedNumber = 0;
56
+ }
48
57
  }
58
+ zeroPrefixed = firstValue.text.startsWith("0");
49
59
  }
50
60
  }
51
61
  // Determine effective style
52
62
  const listStyle = listStyles.includes(style) ? style : (incrementing ? "ordered" : "one");
53
63
  if (listStyle === "zero") {
54
- expected = 0;
64
+ expectedNumber = 0;
55
65
  } else if (listStyle === "one") {
56
- expected = 1;
66
+ expectedNumber = 1;
57
67
  }
58
68
  // Validate each list item marker
59
69
  for (const listItemPrefix of listItemPrefixes) {
60
- const orderedListItemValue = getOrderedListItemValue(listItemPrefix);
61
- const actual = orderedListItemValue.value;
70
+ const expectedText = expectedNumber.toString();
71
+ const { endColumn, startColumn, "number": actualNumber } = getOrderedListItemValue(listItemPrefix);
72
+ const length = endColumn - startColumn;
62
73
  const fixInfo = {
63
- "editColumn": orderedListItemValue.column,
64
- "deleteCount": orderedListItemValue.value.toString().length,
65
- "insertText": expected.toString()
74
+ "editColumn": startColumn,
75
+ "deleteCount": length,
76
+ "insertText": rightAligned ? expectedText.padStart(length, (zeroPrefixed ? "0" : " ")) : expectedText
66
77
  };
67
78
  addErrorDetailIf(
68
79
  onError,
69
80
  listItemPrefix.startLine,
70
- expected,
71
- actual,
81
+ expectedNumber,
82
+ actualNumber,
72
83
  // @ts-ignore
73
84
  "Style: " + listStyleExamples[listStyle],
74
85
  undefined,
@@ -76,7 +87,7 @@ export default {
76
87
  fixInfo
77
88
  );
78
89
  if (listStyle === "ordered") {
79
- expected++;
90
+ expectedNumber++;
80
91
  }
81
92
  }
82
93
  }
package/lib/md030.mjs CHANGED
@@ -10,10 +10,10 @@ export default {
10
10
  "tags": [ "ol", "ul", "whitespace" ],
11
11
  "parser": "micromark",
12
12
  "function": function MD030(params, onError) {
13
- const ulSingle = Number(params.config.ul_single || 1);
14
- const olSingle = Number(params.config.ol_single || 1);
15
- const ulMulti = Number(params.config.ul_multi || 1);
16
- const olMulti = Number(params.config.ol_multi || 1);
13
+ const ulSingle = Number(params.config.ul_single ?? 1);
14
+ const olSingle = Number(params.config.ol_single ?? 1);
15
+ const ulMulti = Number(params.config.ul_multi ?? 1);
16
+ const olMulti = Number(params.config.ol_multi ?? 1);
17
17
  for (const list of filterByTypesCached([ "listOrdered", "listUnordered" ])) {
18
18
  const ordered = (list.type === "listOrdered");
19
19
  const listItemPrefixes =
package/lib/md031.mjs CHANGED
@@ -45,7 +45,7 @@ export default {
45
45
  "parser": "micromark",
46
46
  "function": function MD031(params, onError) {
47
47
  const listItems = params.config.list_items;
48
- const includeListItems = (listItems === undefined) ? true : !!listItems;
48
+ const includeListItems = (listItems === undefined) || !!listItems;
49
49
  const { lines } = params;
50
50
  for (const codeBlock of filterByTypesCached([ "codeFenced" ])) {
51
51
  if (includeListItems || !(getParentOfType(codeBlock, [ "listOrdered", "listUnordered" ]))) {
package/lib/md032.mjs CHANGED
@@ -52,10 +52,11 @@ export default {
52
52
  (token) => !nonContentTokens.has(token.type),
53
53
  (token) => nonContentTokens.has(token.type) ? [] : token.children
54
54
  );
55
- let endLine = list.endLine;
56
- if (flattenedChildren.length > 0) {
57
- endLine = flattenedChildren[flattenedChildren.length - 1].endLine;
58
- }
55
+ const endLine = (
56
+ (flattenedChildren.length > 0) ?
57
+ flattenedChildren[flattenedChildren.length - 1] :
58
+ list
59
+ ).endLine;
59
60
 
60
61
  // Look for a blank line below the list
61
62
  const lastLineNumber = endLine;
package/lib/md033.mjs CHANGED
@@ -4,7 +4,7 @@ import { addError, nextLinesRe } from "../helpers/helpers.cjs";
4
4
  import { getHtmlTagInfo, getParentOfType } from "../helpers/micromark-helpers.cjs";
5
5
  import { filterByTypesCached } from "./cache.mjs";
6
6
 
7
- /** @typedef {import("micromark-extension-gfm-table")} */
7
+ /** @typedef {typeof import("micromark-extension-gfm-table")} _ambient_ */
8
8
 
9
9
  // eslint-disable-next-line jsdoc/reject-any-type
10
10
  const toLowerCaseStringArray = (/** @type {any} */ arr) => Array.isArray(arr) ? arr.map((elm) => String(elm).toLowerCase()) : [];
package/lib/md034.mjs CHANGED
@@ -4,7 +4,7 @@ import { addErrorContext } from "../helpers/helpers.cjs";
4
4
  import { filterByPredicate, getHtmlTagInfo, inHtmlFlow } from "../helpers/micromark-helpers.cjs";
5
5
 
6
6
  /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
7
- /** @typedef {import("micromark-extension-gfm-autolink-literal")} */
7
+ /** @typedef {typeof import("micromark-extension-gfm-autolink-literal")} _ambient_ */
8
8
 
9
9
  /** @type {import("markdownlint").Rule} */
10
10
  export default {
package/lib/md037.mjs CHANGED
@@ -38,8 +38,7 @@ export default {
38
38
  }
39
39
 
40
40
  // Process bare tokens for each emphasis marker type
41
- for (const entry of emphasisTokensByMarker.entries()) {
42
- const [ marker, emphasisTokens ] = entry;
41
+ for (const [ marker, emphasisTokens ] of emphasisTokensByMarker) {
43
42
  for (let i = 0; i + 1 < emphasisTokens.length; i += 2) {
44
43
 
45
44
  // Process start token of start/end pair
package/lib/md039.mjs CHANGED
@@ -1,8 +1,12 @@
1
1
  // @ts-check
2
2
 
3
+ import { re } from "re2js";
3
4
  import { addErrorContext } from "../helpers/helpers.cjs";
4
5
  import { filterByTypesCached } from "./cache.mjs";
5
6
 
7
+ const startRe = re`^[^\S\r\n]+`;
8
+ const endRe = re`[^\S\r\n]+$`;
9
+
6
10
  /**
7
11
  * Adds an error for a label space issue.
8
12
  *
@@ -12,7 +16,7 @@ import { filterByTypesCached } from "./cache.mjs";
12
16
  * @param {boolean} isStart True iff error is at the start of the link.
13
17
  */
14
18
  function addLabelSpaceError(onError, label, labelText, isStart) {
15
- const match = labelText.text.match(isStart ? /^[^\S\r\n]+/ : /[^\S\r\n]+$/);
19
+ const match = (isStart ? startRe : endRe).exec(labelText.text);
16
20
  const range = match ?
17
21
  [
18
22
  (isStart ? (labelText.startColumn) : (labelText.endColumn - match[0].length)),
package/lib/md041.mjs CHANGED
@@ -31,7 +31,7 @@ export default {
31
31
  "parser": "micromark",
32
32
  "function": function MD041(params, onError) {
33
33
  const allowPreamble = !!params.config.allow_preamble;
34
- const level = Number(params.config.level || 1);
34
+ const level = Number(params.config.level ?? 1);
35
35
  const { tokens } = params.parsers.micromark;
36
36
  if (
37
37
  !frontMatterHasTitle(
package/lib/md043.mjs CHANGED
@@ -1,9 +1,32 @@
1
1
  // @ts-check
2
2
 
3
- import { addErrorContext, addErrorDetailIf } from "../helpers/helpers.cjs";
3
+ import { addError, addErrorDetailIf } from "../helpers/helpers.cjs";
4
4
  import { getHeadingLevel, getHeadingText } from "../helpers/micromark-helpers.cjs";
5
5
  import { filterByTypesCached } from "./cache.mjs";
6
6
 
7
+ export const headingsItemsRe = /^(?:[*+?]|\^+|[#%]{1,6}\s+\S.*)$/u;
8
+ const prefixedHeadingPartsRe = /^([#%]{1,6})\s+(\S.*)$/u;
9
+ const loopHeadingRe = /^\^+$/u;
10
+ const canMatchNothingHeadingsRe = /^(?:\*|\^+)$/u;
11
+ const maximumLoopsAllowed = 1000;
12
+
13
+ const headingIsExpected = (/** @type {string} */ actual, /** @type {string} */ expected, /** @type {boolean} */ matchCase) => {
14
+ if (expected.startsWith("%")) {
15
+ // Regular expression comparison
16
+ const [ , actualLevel, actualText ] = prefixedHeadingPartsRe.exec(actual) || [];
17
+ const [ , expectedLevel, expectedText ] = prefixedHeadingPartsRe.exec(expected) || [];
18
+ return (
19
+ (actualLevel.length === expectedLevel.length) &&
20
+ // eslint-disable-next-line unicorn/no-unreadable-new-expression
21
+ (new RegExp(expectedText, `${matchCase ? "" : "i"}u`)).test(actualText)
22
+ );
23
+ }
24
+ // Text comparison
25
+ const actualLower = matchCase ? actual : actual.toLowerCase();
26
+ const expectedLower = matchCase ? expected : expected.toLowerCase();
27
+ return actualLower === expectedLower;
28
+ };
29
+
7
30
  /** @type {import("markdownlint").Rule} */
8
31
  export default {
9
32
  "names": [ "MD043", "required-headings" ],
@@ -11,62 +34,93 @@ export default {
11
34
  "tags": [ "headings" ],
12
35
  "parser": "micromark",
13
36
  "function": function MD043(params, onError) {
14
- const requiredHeadings = params.config.headings;
15
- if (!Array.isArray(requiredHeadings)) {
16
- // Nothing to check; avoid doing any work
37
+ /** @type {string[] | null | undefined} */
38
+ const headings = params.config.headings;
39
+ if (!headings) {
17
40
  return;
18
41
  }
19
- const matchCase = params.config.match_case || false;
42
+ const requiredHeadings = headings.map((heading) => headingsItemsRe.test(heading) ? heading : "[Invalid syntax]");
43
+ const matchCase = Boolean(params.config.match_case);
20
44
  let i = 0;
21
- let matchAny = false;
45
+ let remainingLoopsAllowed = maximumLoopsAllowed;
46
+ let allowAnyMatchFallback = false;
22
47
  let hasError = false;
23
- let anyHeadings = false;
24
- const getExpected = () => String(requiredHeadings[i++] || "[None]");
25
- const handleCase = (/** @type {string} */ str) => (matchCase ? str : str.toLowerCase());
48
+ const getExpected = (/** @type {boolean} */ skipLoopHeadings) => {
49
+ let expected = null;
50
+ do {
51
+ expected = requiredHeadings[i++] || "[No heading]";
52
+ // eslint-disable-next-line no-unmodified-loop-condition
53
+ } while (skipLoopHeadings && loopHeadingRe.test(expected));
54
+ return expected;
55
+ };
26
56
  for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
27
- if (!hasError) {
28
- const headingText = getHeadingText(heading);
29
- const headingLevel = getHeadingLevel(heading);
30
- anyHeadings = true;
31
- const actual = `${"".padEnd(headingLevel, "#")} ${headingText}`;
32
- const expected = getExpected();
33
- if (expected === "*") {
34
- const nextExpected = getExpected();
35
- if (handleCase(nextExpected) !== handleCase(actual)) {
36
- matchAny = true;
37
- i--;
57
+ const headingText = getHeadingText(heading);
58
+ const headingLevel = getHeadingLevel(heading);
59
+ const actual = `${"".padEnd(headingLevel, "#")} ${headingText}`;
60
+ let expected = getExpected(false);
61
+ if (expected.startsWith("^")) {
62
+ // Handle loop
63
+ const current = i;
64
+ const nextExpected = getExpected(true);
65
+ if (headingIsExpected(actual, nextExpected, matchCase)) {
66
+ // No need to loop (again)
67
+ expected = nextExpected;
68
+ } else {
69
+ // Try to loop to previous matching marker
70
+ remainingLoopsAllowed--;
71
+ let j = current - 2;
72
+ for (; j >= 0; j--) {
73
+ if (requiredHeadings[j] === expected) {
74
+ // Perform loop
75
+ i = j + 1;
76
+ expected = getExpected(false);
77
+ break;
78
+ }
38
79
  }
39
- } else if (expected === "+") {
40
- matchAny = true;
41
- } else if (expected === "?") {
42
- // Allow current, match next
43
- } else if (handleCase(expected) === handleCase(actual)) {
44
- matchAny = false;
45
- } else if (matchAny) {
80
+ if (j < 0) {
81
+ // No matching loop marker
82
+ expected = nextExpected;
83
+ }
84
+ }
85
+ }
86
+ if (remainingLoopsAllowed < 0) {
87
+ // Too many loops performed
88
+ addError(onError, heading.startLine, "Too many loops", `Limit: ${maximumLoopsAllowed}`);
89
+ hasError = true;
90
+ break;
91
+ } else if (expected === "*") {
92
+ // Handle "zero or more of anything"
93
+ const nextExpected = getExpected(true);
94
+ if (!headingIsExpected(actual, nextExpected, matchCase)) {
95
+ allowAnyMatchFallback = true;
46
96
  i--;
47
- } else {
48
- addErrorDetailIf(
49
- onError,
50
- heading.startLine,
51
- expected,
52
- actual
53
- );
54
- hasError = true;
55
97
  }
98
+ } else if (expected === "+") {
99
+ // Handle "one or more of anything"
100
+ allowAnyMatchFallback = true;
101
+ } else if (expected === "?") {
102
+ // Handle "exactly one of anything"
103
+ // Nothing to do
104
+ } else if (headingIsExpected(actual, expected, matchCase)) {
105
+ // Handle successful match
106
+ allowAnyMatchFallback = false;
107
+ } else if (allowAnyMatchFallback) {
108
+ // Handle "anything" match by restoring index
109
+ i--;
110
+ } else {
111
+ // Report heading mismatch
112
+ addErrorDetailIf(onError, heading.startLine, expected, actual);
113
+ hasError = true;
114
+ break;
56
115
  }
57
116
  }
58
- const extraHeadings = requiredHeadings.length - i;
117
+ const remainingRequiredHeadings = requiredHeadings.slice(i).filter((heading) => !canMatchNothingHeadingsRe.test(heading));
59
118
  if (
60
119
  !hasError &&
61
- ((extraHeadings > 1) ||
62
- ((extraHeadings === 1) && (requiredHeadings[i] !== "*"))) &&
63
- (anyHeadings || !requiredHeadings.every((heading) => heading === "*"))
120
+ (remainingRequiredHeadings.length > 0)
64
121
  ) {
65
- addErrorContext(
66
- onError,
67
- params.lines.length,
68
- requiredHeadings[i]
69
- );
122
+ // Report unmatched heading(s)
123
+ addErrorDetailIf(onError, params.lines.length, remainingRequiredHeadings[0], "[End of file]");
70
124
  }
71
125
  }
72
126
  };
package/lib/md044.mjs CHANGED
@@ -24,10 +24,10 @@ export default {
24
24
  }
25
25
  const codeBlocks = params.config.code_blocks;
26
26
  const includeCodeBlocks =
27
- (codeBlocks === undefined) ? true : !!codeBlocks;
27
+ (codeBlocks === undefined) || !!codeBlocks;
28
28
  const htmlElements = params.config.html_elements;
29
29
  const includeHtmlElements =
30
- (htmlElements === undefined) ? true : !!htmlElements;
30
+ (htmlElements === undefined) || !!htmlElements;
31
31
  const scannedTypes = new Set([ "data" ]);
32
32
  if (includeCodeBlocks) {
33
33
  scannedTypes.add("codeFlowValue");
package/lib/md048.mjs CHANGED
@@ -30,9 +30,7 @@ export default {
30
30
  let expectedStyle = style;
31
31
  const codeFenceds = filterByTypesCached([ "codeFenced" ]);
32
32
  for (const codeFenced of codeFenceds) {
33
- const codeFencedFenceSequence =
34
- getDescendantsByType(codeFenced, [ "codeFencedFence", "codeFencedFenceSequence" ])[0];
35
- const { startLine, text } = codeFencedFenceSequence;
33
+ const { startLine, text } = getDescendantsByType(codeFenced, [ "codeFencedFence", "codeFencedFenceSequence" ])[0];
36
34
  if (expectedStyle === "consistent") {
37
35
  expectedStyle = fencedCodeBlockStyleFor(text);
38
36
  }
package/lib/md051.mjs CHANGED
@@ -7,7 +7,7 @@ import { filterByTypesCached } from "./cache.mjs";
7
7
  // Regular expression for identifying HTML anchor names
8
8
  const idRe = getHtmlAttributeRe("id");
9
9
  const nameRe = getHtmlAttributeRe("name");
10
- const anchorRe = /\{(#[a-z\d]+(?:[-_][a-z\d]+)*)\}/gu;
10
+ const anchorRe = /\{(#[a-z\d]+(?:[-_]+[a-z\d]+)*)\}/gu;
11
11
  const lineFragmentRe = /^#(?:L\d+(?:C\d+)?-L\d+(?:C\d+)?|L\d+)$/;
12
12
 
13
13
  // Sets for filtering heading tokens during conversion
@@ -17,8 +17,7 @@ const tokensInclude = new Set(
17
17
  );
18
18
 
19
19
  /**
20
- * Converts a Markdown heading into an HTML fragment according to the rules
21
- * used by GitHub.
20
+ * Converts a Markdown heading into an HTML fragment according to the rules used by GitHub.
22
21
  *
23
22
  * @param {import("markdownlint").MicromarkToken} headingText Heading text token.
24
23
  * @returns {string} Fragment string for heading.
@@ -141,6 +140,7 @@ export default {
141
140
  };
142
141
  }
143
142
  const textLower = text.toLowerCase();
143
+ // eslint-disable-next-line unicorn/prefer-iterator-helpers
144
144
  const mixedCaseKey = [ ...fragments.keys() ]
145
145
  .find((key) => textLower === key.toLowerCase());
146
146
  if (mixedCaseKey) {
package/lib/md055.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  // @ts-check
2
2
 
3
3
  import { addErrorDetailIf } from "../helpers/helpers.cjs";
4
+ import { getParentOfType } from "../helpers/micromark-helpers.cjs";
4
5
  import { filterByTypesCached } from "./cache.mjs";
5
6
 
6
7
  /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
7
- /** @typedef {import("micromark-extension-gfm-table")} */
8
+ /** @typedef {typeof import("micromark-extension-gfm-table")} _ambient_ */
8
9
 
9
10
  const whitespaceTypes = new Set([ "linePrefix", "whitespace" ]);
10
11
  const ignoreWhitespace = (/** @type {MicromarkToken[]} */ tokens) => tokens.filter(
@@ -27,8 +28,17 @@ export default {
27
28
  ((expectedStyle !== "no_leading_or_trailing") && (expectedStyle !== "trailing_only"));
28
29
  let expectedTrailingPipe =
29
30
  ((expectedStyle !== "no_leading_or_trailing") && (expectedStyle !== "leading_only"));
31
+ let currentTable = null;
30
32
  const rows = filterByTypesCached([ "tableDelimiterRow", "tableRow" ]);
31
33
  for (const row of rows) {
34
+ if (style === "per_table") {
35
+ // Reset style for each new table
36
+ const rowTable = getParentOfType(row, [ "table" ]);
37
+ if (rowTable !== currentTable) {
38
+ expectedStyle = "consistent";
39
+ currentTable = rowTable;
40
+ }
41
+ }
32
42
  // The following uses of first/lastOrNothing lack fallback handling
33
43
  // because it seems not to be possible (i.e., 0% coverage)
34
44
  const firstCell = firstOrNothing(row.children);
package/lib/md056.mjs CHANGED
@@ -6,7 +6,7 @@ import { filterByTypesCached } from "./cache.mjs";
6
6
 
7
7
  const makeRange = (/** @type {number} */ start, /** @type {number} */ end) => [ start, end - start + 1 ];
8
8
 
9
- /** @typedef {import("micromark-extension-gfm-table")} */
9
+ /** @typedef {typeof import("micromark-extension-gfm-table")} _ambient_ */
10
10
 
11
11
  /** @type {import("markdownlint").Rule} */
12
12
  export default {
package/lib/md058.mjs CHANGED
@@ -4,7 +4,7 @@ import { addErrorContext, isBlankLine } from "../helpers/helpers.cjs";
4
4
  import { getBlockQuotePrefixText } from "../helpers/micromark-helpers.cjs";
5
5
  import { filterByTypesCached } from "./cache.mjs";
6
6
 
7
- /** @typedef {import("micromark-extension-gfm-table")} */
7
+ /** @typedef {typeof import("micromark-extension-gfm-table")} _ambient_ */
8
8
 
9
9
  /** @type {import("markdownlint").Rule} */
10
10
  export default {