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
@@ -2,11 +2,26 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addError, emphasisOrStrongStyleFor } = require("../helpers");
6
- const { filterByPredicate, tokenIfType } = require("../helpers/micromark.cjs");
5
+ const { addError } = require("../helpers");
6
+ const { filterByPredicate, getDescendantsByType } = require("../helpers/micromark-helpers.cjs");
7
7
 
8
8
  const intrawordRe = /^\w$/;
9
9
 
10
+ /**
11
+ * Return the string representation of a emphasis or strong markup character.
12
+ *
13
+ * @param {string} markup Emphasis or strong string.
14
+ * @returns {"asterisk" | "underscore"} String representation.
15
+ */
16
+ function emphasisOrStrongStyleFor(markup) {
17
+ switch (markup[0]) {
18
+ case "*":
19
+ return "asterisk";
20
+ default:
21
+ return "underscore";
22
+ }
23
+ };
24
+
10
25
  /**
11
26
  * @param {import("./markdownlint").RuleParams} params Rule parameters.
12
27
  * @param {import("./markdownlint").RuleOnError} onError Error-reporting callback.
@@ -18,21 +33,16 @@ const intrawordRe = /^\w$/;
18
33
  */
19
34
  const impl =
20
35
  (params, onError, type, typeSequence, asterisk, underline, style = "consistent") => {
21
- // eslint-disable-next-line jsdoc/valid-types
22
- /** @type import("../helpers/micromark.cjs").Token[] */
23
- const micromarkTokens =
24
- // @ts-ignore
25
- params.parsers.micromark.tokens;
26
- const { lines } = params;
36
+ const { lines, parsers } = params;
27
37
  const emphasisTokens = filterByPredicate(
28
- micromarkTokens,
38
+ parsers.micromark.tokens,
29
39
  (token) => token.type === type,
30
40
  (token) => ((token.type === "htmlFlow") ? [] : token.children)
31
41
  );
32
42
  for (const token of emphasisTokens) {
33
- const { children } = token;
34
- const startSequence = tokenIfType(children[0], typeSequence);
35
- const endSequence = tokenIfType(children[children.length - 1], typeSequence);
43
+ const sequences = getDescendantsByType(token, [ typeSequence ]);
44
+ const startSequence = sequences[0];
45
+ const endSequence = sequences[sequences.length - 1];
36
46
  if (startSequence && endSequence) {
37
47
  const markupStyle = emphasisOrStrongStyleFor(startSequence.text);
38
48
  if (style === "consistent") {
package/lib/md051.js CHANGED
@@ -2,10 +2,9 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addError, addErrorDetailIf, getHtmlAttributeRe } =
6
- require("../helpers");
7
- const { filterByPredicate, filterByTypes, getHtmlTagInfo } =
8
- require("../helpers/micromark.cjs");
5
+ const { addError, getHtmlAttributeRe } = require("../helpers");
6
+ const { filterByPredicate, filterByTypes, getHtmlTagInfo } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
9
8
 
10
9
  // Regular expression for identifying HTML anchor names
11
10
  const idRe = getHtmlAttributeRe("id");
@@ -23,7 +22,7 @@ const tokensInclude = new Set(
23
22
  * Converts a Markdown heading into an HTML fragment according to the rules
24
23
  * used by GitHub.
25
24
  *
26
- * @param {import("../helpers/micromark.cjs").Token} headingText Heading text token.
25
+ * @param {import("../helpers/micromark-helpers.cjs").Token} headingText Heading text token.
27
26
  * @returns {string} Fragment string for heading.
28
27
  */
29
28
  function convertHeadingToHTMLFragment(headingText) {
@@ -52,7 +51,7 @@ function convertHeadingToHTMLFragment(headingText) {
52
51
  /**
53
52
  * Unescapes the text of a String-type micromark Token.
54
53
  *
55
- * @param {import("../helpers/micromark.cjs").Token} token String-type micromark Token.
54
+ * @param {import("../helpers/micromark-helpers.cjs").Token} token String-type micromark Token.
56
55
  * @returns {string} Unescaped token text.
57
56
  */
58
57
  function unescapeStringTokenText(token) {
@@ -69,15 +68,11 @@ module.exports = {
69
68
  "tags": [ "links" ],
70
69
  "parser": "micromark",
71
70
  "function": function MD051(params, onError) {
72
- // eslint-disable-next-line jsdoc/valid-types
73
- /** @type import("../helpers/micromark.cjs").Token[] */
74
- const micromarkTokens =
75
- // @ts-ignore
76
- params.parsers.micromark.tokens;
71
+ const ignoreCase = params.config.ignore_case || false;
77
72
  const fragments = new Map();
78
73
 
79
74
  // Process headings
80
- const headingTexts = filterByTypes(micromarkTokens, [ "atxHeadingText", "setextHeadingText" ]);
75
+ const headingTexts = filterByTypesCached([ "atxHeadingText", "setextHeadingText" ]);
81
76
  for (const headingText of headingTexts) {
82
77
  const fragment = convertHeadingToHTMLFragment(headingText);
83
78
  if (fragment !== "#") {
@@ -97,7 +92,7 @@ module.exports = {
97
92
  }
98
93
 
99
94
  // Process HTML anchors
100
- for (const token of filterByTypes(micromarkTokens, [ "htmlText" ])) {
95
+ for (const token of filterByTypesCached([ "htmlText" ], true)) {
101
96
  const htmlTagInfo = getHtmlTagInfo(token);
102
97
  if (htmlTagInfo && !htmlTagInfo.close) {
103
98
  const anchorMatch = idRe.exec(token.text) ||
@@ -110,13 +105,13 @@ module.exports = {
110
105
 
111
106
  // Process link and definition fragments
112
107
  // eslint-disable-next-line jsdoc/valid-types
113
- /** @type import("../helpers/micromark.cjs").TokenType[][] */
108
+ /** @type import("../helpers/micromark-helpers.cjs").TokenType[][] */
114
109
  const parentChilds = [
115
110
  [ "link", "resourceDestinationString" ],
116
111
  [ "definition", "definitionDestinationString" ]
117
112
  ];
118
113
  for (const [ parentType, definitionType ] of parentChilds) {
119
- const links = filterByTypes(micromarkTokens, [ parentType ]);
114
+ const links = filterByTypesCached([ parentType ]);
120
115
  for (const link of links) {
121
116
  const definitions = filterByTypes(link.children, [ definitionType ]);
122
117
  for (const definition of definitions) {
@@ -129,11 +124,8 @@ module.exports = {
129
124
  !fragments.has(encodedText) &&
130
125
  !lineFragmentRe.test(encodedText)
131
126
  ) {
132
- // eslint-disable-next-line no-undef-init
133
127
  let context = undefined;
134
- // eslint-disable-next-line no-undef-init
135
128
  let range = undefined;
136
- // eslint-disable-next-line no-undef-init
137
129
  let fixInfo = undefined;
138
130
  if (link.startLine === link.endLine) {
139
131
  context = link.text;
@@ -149,16 +141,16 @@ module.exports = {
149
141
  if (mixedCaseKey) {
150
142
  // @ts-ignore
151
143
  (fixInfo || {}).insertText = mixedCaseKey;
152
- addErrorDetailIf(
153
- onError,
154
- link.startLine,
155
- mixedCaseKey,
156
- text,
157
- undefined,
158
- context,
159
- range,
160
- fixInfo
161
- );
144
+ if (!ignoreCase && (mixedCaseKey !== text)) {
145
+ addError(
146
+ onError,
147
+ link.startLine,
148
+ `Expected: ${mixedCaseKey}; Actual: ${text}`,
149
+ context,
150
+ range,
151
+ fixInfo
152
+ );
153
+ }
162
154
  } else {
163
155
  addError(
164
156
  onError,
package/lib/md052.js CHANGED
@@ -3,7 +3,7 @@
3
3
  "use strict";
4
4
 
5
5
  const { addError } = require("../helpers");
6
- const { referenceLinkImageData } = require("./cache");
6
+ const { getReferenceLinkImageData } = require("./cache");
7
7
 
8
8
  // eslint-disable-next-line jsdoc/valid-types
9
9
  /** @type import("./markdownlint").Rule */
@@ -16,7 +16,7 @@ module.exports = {
16
16
  "function": function MD052(params, onError) {
17
17
  const { config, lines } = params;
18
18
  const shortcutSyntax = config.shortcut_syntax || false;
19
- const { definitions, references, shortcuts } = referenceLinkImageData();
19
+ const { definitions, references, shortcuts } = getReferenceLinkImageData();
20
20
  const entries = shortcutSyntax ?
21
21
  [ ...references.entries(), ...shortcuts.entries() ] :
22
22
  references.entries();
package/lib/md053.js CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { addError, ellipsify, linkReferenceDefinitionRe } =
6
- require("../helpers");
7
- const { referenceLinkImageData } = require("./cache");
5
+ const { addError, ellipsify } = require("../helpers");
6
+ const { getReferenceLinkImageData } = require("./cache");
7
+
8
+ const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])\]:/;
8
9
 
9
10
  // eslint-disable-next-line jsdoc/valid-types
10
11
  /** @type import("./markdownlint").Rule */
@@ -17,7 +18,7 @@ module.exports = {
17
18
  const ignored = new Set(params.config.ignored_definitions || [ "//" ]);
18
19
  const lines = params.lines;
19
20
  const { references, shortcuts, definitions, duplicateDefinitions } =
20
- referenceLinkImageData();
21
+ getReferenceLinkImageData();
21
22
  const singleLineDefinition = (line) => (
22
23
  line.replace(linkReferenceDefinitionRe, "").trim().length > 0
23
24
  );
@@ -39,7 +40,7 @@ module.exports = {
39
40
  `Unused link or image reference definition: "${label}"`,
40
41
  ellipsify(line),
41
42
  [ 1, line.length ],
42
- singleLineDefinition(line) ? deleteFixInfo : 0
43
+ singleLineDefinition(line) ? deleteFixInfo : undefined
43
44
  );
44
45
  }
45
46
  }
@@ -54,7 +55,7 @@ module.exports = {
54
55
  `Duplicate link or image reference definition: "${label}"`,
55
56
  ellipsify(line),
56
57
  [ 1, line.length ],
57
- singleLineDefinition(line) ? deleteFixInfo : 0
58
+ singleLineDefinition(line) ? deleteFixInfo : undefined
58
59
  );
59
60
  }
60
61
  }
package/lib/md054.js CHANGED
@@ -3,9 +3,8 @@
3
3
  "use strict";
4
4
 
5
5
  const { addErrorContext, nextLinesRe } = require("../helpers");
6
- const { filterByTypes, filterByPredicate, getTokenTextByType } =
7
- require("../helpers/micromark.cjs");
8
- const { referenceLinkImageData } = require("./cache");
6
+ const { getDescendantsByType } = require("../helpers/micromark-helpers.cjs");
7
+ const { getReferenceLinkImageData, filterByTypesCached } = require("./cache");
9
8
 
10
9
  const backslashEscapeRe = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g;
11
10
  const removeBackslashEscapes = (text) => text.replace(backslashEscapeRe, "$1");
@@ -40,35 +39,29 @@ module.exports = {
40
39
  // Everything allowed, nothing to check
41
40
  return;
42
41
  }
43
- // eslint-disable-next-line jsdoc/valid-types
44
- /** @type import("../helpers/micromark.cjs").Token[] */
45
- const micromarkTokens =
46
- // @ts-ignore
47
- params.parsers.micromark.tokens;
48
- const { definitions } = referenceLinkImageData();
49
- const links = filterByTypes(micromarkTokens, [ "autolink", "image", "link" ]);
42
+ const { definitions } = getReferenceLinkImageData();
43
+ const links = filterByTypesCached([ "autolink", "image", "link" ]);
50
44
  for (const link of links) {
51
45
  let label = null;
52
46
  let destination = null;
53
47
  const {
54
- children, endColumn, endLine, startColumn, startLine, text, type
48
+ endColumn, endLine, startColumn, startLine, text, type
55
49
  } = link;
56
50
  const image = (type === "image");
57
51
  let isError = false;
58
52
  if (type === "autolink") {
59
53
  // link kind is an autolink
60
- destination = getTokenTextByType(children, "autolinkProtocol");
54
+ destination = getDescendantsByType(link, [ "autolinkProtocol" ])[0].text;
61
55
  label = destination;
62
56
  isError = !autolink;
63
57
  } else {
64
58
  // link type is "image" or "link"
65
- const descendents = filterByPredicate(children);
66
- label = getTokenTextByType(descendents, "labelText");
59
+ label = getDescendantsByType(link, [ "label", "labelText" ])[0].text;
67
60
  destination =
68
- getTokenTextByType(descendents, "resourceDestinationString");
61
+ getDescendantsByType(link, [ "resource", "resourceDestination", [ "resourceDestinationLiteral", "resourceDestinationRaw" ], "resourceDestinationString" ])[0]?.text;
69
62
  if (destination) {
70
63
  // link kind is an inline link
71
- const title = getTokenTextByType(descendents, "resourceTitleString");
64
+ const title = getDescendantsByType(link, [ "resource", "resourceTitle", "resourceTitleString" ])[0]?.text;
72
65
  isError = !inline || (
73
66
  !urlInline &&
74
67
  autolink &&
@@ -79,9 +72,9 @@ module.exports = {
79
72
  );
80
73
  } else {
81
74
  // link kind is a full/collapsed/shortcut reference link
82
- const isShortcut = !children.some((t) => t.type === "reference");
83
- const referenceString = getTokenTextByType(descendents, "referenceString");
84
- const isCollapsed = (referenceString === null);
75
+ const isShortcut = getDescendantsByType(link, [ "reference" ]).length === 0;
76
+ const referenceString = getDescendantsByType(link, [ "reference", "referenceString" ])[0]?.text;
77
+ const isCollapsed = (referenceString === undefined);
85
78
  const definition = definitions.get(referenceString || label);
86
79
  destination = definition && definition[1];
87
80
  isError = destination &&
@@ -89,8 +82,8 @@ module.exports = {
89
82
  }
90
83
  }
91
84
  if (isError) {
92
- let range = null;
93
- let fixInfo = null;
85
+ let range = undefined;
86
+ let fixInfo = undefined;
94
87
  if (startLine === endLine) {
95
88
  range = [ startColumn, endColumn - startColumn ];
96
89
  let insertText = null;
@@ -119,8 +112,8 @@ module.exports = {
119
112
  onError,
120
113
  startLine,
121
114
  text.replace(nextLinesRe, ""),
122
- null,
123
- null,
115
+ undefined,
116
+ undefined,
124
117
  range,
125
118
  fixInfo
126
119
  );
package/lib/md055.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 whitespaceTypes = new Set([ "linePrefix", "whitespace" ]);
9
9
  const ignoreWhitespace = (tokens) => tokens.filter(
@@ -27,54 +27,46 @@ module.exports = {
27
27
  ((expectedStyle !== "no_leading_or_trailing") && (expectedStyle !== "trailing_only"));
28
28
  let expectedTrailingPipe =
29
29
  ((expectedStyle !== "no_leading_or_trailing") && (expectedStyle !== "leading_only"));
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 tables = filterByTypes(micromarkTokens, [ "table" ]);
36
- for (const table of tables) {
37
- const rows = filterByTypes(table.children, [ "tableDelimiterRow", "tableRow" ]);
38
- for (const row of rows) {
39
- // The following uses of first/lastOrNothing lack fallback handling
40
- // because it seems not to be possible (i.e., 0% coverage)
41
- const firstCell = firstOrNothing(row.children);
42
- const leadingToken = firstOrNothing(ignoreWhitespace(firstCell.children));
43
- const actualLeadingPipe = (leadingToken.type === "tableCellDivider");
44
- const lastCell = lastOrNothing(row.children);
45
- const trailingToken = lastOrNothing(ignoreWhitespace(lastCell.children));
46
- const actualTrailingPipe = (trailingToken.type === "tableCellDivider");
47
- const actualStyle = actualLeadingPipe ?
48
- (actualTrailingPipe ? "leading_and_trailing" : "leading_only") :
49
- (actualTrailingPipe ? "trailing_only" : "no_leading_or_trailing");
50
- if (expectedStyle === "consistent") {
51
- expectedStyle = actualStyle;
52
- expectedLeadingPipe = actualLeadingPipe;
53
- expectedTrailingPipe = actualTrailingPipe;
54
- }
55
- if (actualLeadingPipe !== expectedLeadingPipe) {
56
- addErrorDetailIf(
57
- onError,
58
- firstCell.startLine,
59
- expectedStyle,
60
- actualStyle,
61
- `${expectedLeadingPipe ? "Missing" : "Unexpected"} leading pipe`,
62
- undefined,
63
- makeRange(row.startColumn, firstCell.startColumn)
64
- );
65
- }
66
- if (actualTrailingPipe !== expectedTrailingPipe) {
67
- addErrorDetailIf(
68
- onError,
69
- lastCell.endLine,
70
- expectedStyle,
71
- actualStyle,
72
- `${expectedTrailingPipe ? "Missing" : "Unexpected"} trailing pipe`,
73
- undefined,
74
- makeRange(lastCell.endColumn - 1, row.endColumn - 1)
75
- );
76
- }
30
+ const rows = filterByTypesCached([ "tableDelimiterRow", "tableRow" ]);
31
+ for (const row of rows) {
32
+ // The following uses of first/lastOrNothing lack fallback handling
33
+ // because it seems not to be possible (i.e., 0% coverage)
34
+ const firstCell = firstOrNothing(row.children);
35
+ const leadingToken = firstOrNothing(ignoreWhitespace(firstCell.children));
36
+ const actualLeadingPipe = (leadingToken.type === "tableCellDivider");
37
+ const lastCell = lastOrNothing(row.children);
38
+ const trailingToken = lastOrNothing(ignoreWhitespace(lastCell.children));
39
+ const actualTrailingPipe = (trailingToken.type === "tableCellDivider");
40
+ const actualStyle = actualLeadingPipe ?
41
+ (actualTrailingPipe ? "leading_and_trailing" : "leading_only") :
42
+ (actualTrailingPipe ? "trailing_only" : "no_leading_or_trailing");
43
+ if (expectedStyle === "consistent") {
44
+ expectedStyle = actualStyle;
45
+ expectedLeadingPipe = actualLeadingPipe;
46
+ expectedTrailingPipe = actualTrailingPipe;
47
+ }
48
+ if (actualLeadingPipe !== expectedLeadingPipe) {
49
+ addErrorDetailIf(
50
+ onError,
51
+ firstCell.startLine,
52
+ expectedStyle,
53
+ actualStyle,
54
+ `${expectedLeadingPipe ? "Missing" : "Unexpected"} leading pipe`,
55
+ undefined,
56
+ makeRange(row.startColumn, firstCell.startColumn)
57
+ );
58
+ }
59
+ if (actualTrailingPipe !== expectedTrailingPipe) {
60
+ addErrorDetailIf(
61
+ onError,
62
+ lastCell.endLine,
63
+ expectedStyle,
64
+ actualStyle,
65
+ `${expectedTrailingPipe ? "Missing" : "Unexpected"} trailing pipe`,
66
+ undefined,
67
+ makeRange(lastCell.endColumn - 1, row.endColumn - 1)
68
+ );
77
69
  }
78
70
  }
79
71
  }
80
- }
72
+ };
package/lib/md056.js CHANGED
@@ -3,7 +3,8 @@
3
3
  "use strict";
4
4
 
5
5
  const { addErrorDetailIf } = require("../helpers");
6
- const { filterByTypes } = require("../helpers/micromark.cjs");
6
+ const { getParentOfType } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
7
8
 
8
9
  const makeRange = (start, end) => [ start, end - start + 1 ];
9
10
 
@@ -15,38 +16,36 @@ module.exports = {
15
16
  "tags": [ "table" ],
16
17
  "parser": "micromark",
17
18
  "function": function MD056(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
- const tables = filterByTypes(micromarkTokens, [ "table" ]);
24
- for (const table of tables) {
25
- const rows = filterByTypes(table.children, [ "tableDelimiterRow", "tableRow" ]);
26
- let expectedCount = 0;
27
- for (const row of rows) {
28
- const cells = filterByTypes(row.children, [ "tableData", "tableDelimiter", "tableHeader" ]);
29
- const actualCount = cells.length;
30
- expectedCount ||= actualCount;
31
- let detail = null;
32
- let range = null;
33
- if (actualCount < expectedCount) {
34
- detail = "Too few cells, row will be missing data";
35
- range = [ row.endColumn - 1, 1 ];
36
- } else if (expectedCount < actualCount) {
37
- detail = "Too many cells, extra data will be missing";
38
- range = makeRange(cells[expectedCount].startColumn, row.endColumn - 1);
39
- }
40
- addErrorDetailIf(
41
- onError,
42
- row.endLine,
43
- expectedCount,
44
- actualCount,
45
- detail,
46
- null,
47
- range
48
- );
19
+ const rows = filterByTypesCached([ "tableDelimiterRow", "tableRow" ]);
20
+ let expectedCount = 0;
21
+ let currentTable = null;
22
+ for (const row of rows) {
23
+ const table = getParentOfType(row, [ "table" ]);
24
+ if (currentTable !== table) {
25
+ expectedCount = 0;
26
+ currentTable = table;
49
27
  }
28
+ const cells = row.children.filter((child) => [ "tableData", "tableDelimiter", "tableHeader" ].includes(child.type));
29
+ const actualCount = cells.length;
30
+ expectedCount ||= actualCount;
31
+ let detail = undefined;
32
+ let range = undefined;
33
+ if (actualCount < expectedCount) {
34
+ detail = "Too few cells, row will be missing data";
35
+ range = [ row.endColumn - 1, 1 ];
36
+ } else if (expectedCount < actualCount) {
37
+ detail = "Too many cells, extra data will be missing";
38
+ range = makeRange(cells[expectedCount].startColumn, row.endColumn - 1);
39
+ }
40
+ addErrorDetailIf(
41
+ onError,
42
+ row.endLine,
43
+ expectedCount,
44
+ actualCount,
45
+ detail,
46
+ undefined,
47
+ range
48
+ );
50
49
  }
51
50
  }
52
- }
51
+ };
package/lib/md058.js ADDED
@@ -0,0 +1,58 @@
1
+ // @ts-check
2
+
3
+ "use strict";
4
+
5
+ const { addErrorContext, isBlankLine } = require("../helpers");
6
+ const { getBlockQuotePrefixText } = require("../helpers/micromark-helpers.cjs");
7
+ const { filterByTypesCached } = require("./cache");
8
+
9
+ // eslint-disable-next-line jsdoc/valid-types
10
+ /** @type import("./markdownlint").Rule */
11
+ module.exports = {
12
+ "names": [ "MD058", "blanks-around-tables" ],
13
+ "description": "Tables should be surrounded by blank lines",
14
+ "tags": [ "table" ],
15
+ "parser": "micromark",
16
+ "function": function MD058(params, onError) {
17
+ const { lines } = params;
18
+ const blockQuotePrefixes = filterByTypesCached([ "blockQuotePrefix", "linePrefix" ]);
19
+
20
+ // For every table...
21
+ const tables = filterByTypesCached([ "table" ]);
22
+ for (const table of tables) {
23
+
24
+ // Look for a blank line above the table
25
+ const firstLineNumber = table.startLine;
26
+ if (!isBlankLine(lines[firstLineNumber - 2])) {
27
+ addErrorContext(
28
+ onError,
29
+ firstLineNumber,
30
+ lines[firstLineNumber - 1].trim(),
31
+ undefined,
32
+ undefined,
33
+ undefined,
34
+ {
35
+ "insertText": getBlockQuotePrefixText(blockQuotePrefixes, firstLineNumber)
36
+ }
37
+ );
38
+ }
39
+
40
+ // Look for a blank line below the table
41
+ const lastLineNumber = table.endLine;
42
+ if (!isBlankLine(lines[lastLineNumber])) {
43
+ addErrorContext(
44
+ onError,
45
+ lastLineNumber,
46
+ lines[lastLineNumber - 1].trim(),
47
+ undefined,
48
+ undefined,
49
+ undefined,
50
+ {
51
+ "lineNumber": lastLineNumber + 1,
52
+ "insertText": getBlockQuotePrefixText(blockQuotePrefixes, lastLineNumber)
53
+ }
54
+ );
55
+ }
56
+ }
57
+ }
58
+ };
package/lib/rules.js CHANGED
@@ -4,6 +4,11 @@
4
4
 
5
5
  const { homepage, version } = require("./constants");
6
6
 
7
+ // @ts-ignore
8
+ const [ md019, md021 ] = require("./md019-md021");
9
+ // @ts-ignore
10
+ const [ md049, md050 ] = require("./md049-md050");
11
+
7
12
  const rules = [
8
13
  require("./md001"),
9
14
  // md002: Deprecated and removed
@@ -19,9 +24,9 @@ const rules = [
19
24
  require("./md013"),
20
25
  require("./md014"),
21
26
  require("./md018"),
22
- require("./md019"),
27
+ md019,
23
28
  require("./md020"),
24
- require("./md021"),
29
+ md021,
25
30
  require("./md022"),
26
31
  require("./md023"),
27
32
  require("./md024"),
@@ -49,14 +54,16 @@ const rules = [
49
54
  require("./md046"),
50
55
  require("./md047"),
51
56
  require("./md048"),
52
- ...require("./md049-md050"),
57
+ md049,
58
+ md050,
53
59
  require("./md051"),
54
60
  require("./md052"),
55
61
  require("./md053"),
56
62
  require("./md054"),
57
63
  require("./md055"),
58
- require("./md056")
64
+ require("./md056"),
59
65
  // md057: See https://github.com/markdownlint/markdownlint
66
+ require("./md058")
60
67
  ];
61
68
  for (const rule of rules) {
62
69
  const name = rule.names[0].toLowerCase();