markdownlint 0.38.0 → 0.40.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 (60) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +144 -75
  4. package/doc/Rules.md +174 -18
  5. package/doc/md001.md +14 -0
  6. package/doc/md009.md +8 -9
  7. package/doc/md010.md +6 -0
  8. package/doc/md029.md +2 -0
  9. package/doc/md033.md +7 -1
  10. package/doc/md051.md +10 -5
  11. package/doc/md055.md +1 -1
  12. package/doc/md056.md +1 -1
  13. package/doc/md059.md +4 -1
  14. package/doc/md060.md +118 -0
  15. package/helpers/README.md +2 -2
  16. package/helpers/helpers.cjs +171 -15
  17. package/helpers/micromark-helpers.cjs +11 -7
  18. package/helpers/package.json +1 -1
  19. package/lib/cache.mjs +6 -2
  20. package/lib/configuration-strict.d.ts +1351 -205
  21. package/lib/constants.mjs +6 -5
  22. package/lib/defer-require.cjs +2 -0
  23. package/lib/exports.d.mts +1 -2
  24. package/lib/exports.mjs +1 -2
  25. package/lib/markdownit.cjs +2 -1
  26. package/lib/markdownlint.d.mts +101 -51
  27. package/lib/markdownlint.mjs +217 -181
  28. package/lib/md001.mjs +6 -2
  29. package/lib/md004.mjs +7 -18
  30. package/lib/md007.mjs +2 -1
  31. package/lib/md009.mjs +9 -5
  32. package/lib/md010.mjs +1 -1
  33. package/lib/md011.mjs +8 -6
  34. package/lib/md013.mjs +14 -27
  35. package/lib/md022.mjs +5 -2
  36. package/lib/md029.mjs +25 -14
  37. package/lib/md031.mjs +0 -1
  38. package/lib/md032.mjs +3 -1
  39. package/lib/md033.mjs +28 -20
  40. package/lib/md034.mjs +2 -1
  41. package/lib/md036.mjs +5 -3
  42. package/lib/md037.mjs +10 -7
  43. package/lib/md042.mjs +4 -2
  44. package/lib/md043.mjs +2 -2
  45. package/lib/md044.mjs +1 -1
  46. package/lib/md046.mjs +6 -6
  47. package/lib/md053.mjs +1 -1
  48. package/lib/md054.mjs +7 -5
  49. package/lib/md055.mjs +7 -6
  50. package/lib/md056.mjs +1 -1
  51. package/lib/md060.mjs +170 -0
  52. package/lib/micromark-parse.mjs +5 -3
  53. package/lib/node-imports-browser.mjs +2 -0
  54. package/lib/parse-configuration.mjs +5 -4
  55. package/lib/rules.mjs +3 -1
  56. package/package.json +18 -18
  57. package/schema/.markdownlint.jsonc +69 -54
  58. package/schema/.markdownlint.yaml +66 -53
  59. package/schema/markdownlint-config-schema-strict.json +4709 -1537
  60. package/schema/markdownlint-config-schema.json +4709 -1537
package/doc/md001.md CHANGED
@@ -4,6 +4,11 @@ Tags: `headings`
4
4
 
5
5
  Aliases: `heading-increment`
6
6
 
7
+ Parameters:
8
+
9
+ - `front_matter_title`: RegExp for matching title in front matter (`string`,
10
+ default `^\s*title\s*[:=]`)
11
+
7
12
  This rule is triggered when you skip heading levels in a Markdown document, for
8
13
  example:
9
14
 
@@ -32,6 +37,15 @@ level at a time:
32
37
  ### Another Heading 3
33
38
  ```
34
39
 
40
+ If [YAML](https://en.wikipedia.org/wiki/YAML) front matter is present and
41
+ contains a `title` property (commonly used with blog posts), this rule treats
42
+ that as a top level heading and will report a violation if the actual first
43
+ heading is not a level 2 heading. To use a different property name in the
44
+ front matter, specify the text of a regular expression via the
45
+ `front_matter_title` parameter. To disable the use of front matter by this
46
+ rule, specify `""` for `front_matter_title`. When front matter is not present,
47
+ the first heading can be any level.
48
+
35
49
  Rationale: Headings represent the structure of a document and can be confusing
36
50
  when skipped - especially for accessibility scenarios. More information:
37
51
  <https://www.w3.org/WAI/tutorials/page-structure/headings/>.
package/doc/md009.md CHANGED
@@ -7,6 +7,7 @@ Aliases: `no-trailing-spaces`
7
7
  Parameters:
8
8
 
9
9
  - `br_spaces`: Spaces for line break (`integer`, default `2`)
10
+ - `code_blocks`: Include code blocks (`boolean`, default `false`)
10
11
  - `list_item_empty_lines`: Allow spaces for empty lines in list items
11
12
  (`boolean`, default `false`)
12
13
  - `strict`: Include unnecessary breaks (`boolean`, default `false`)
@@ -16,21 +17,19 @@ Fixable: Some violations can be fixed by tooling
16
17
  This rule is triggered on any lines that end with unexpected whitespace. To fix
17
18
  this, remove the trailing space from the end of the line.
18
19
 
19
- Note: Trailing space is allowed in indented and fenced code blocks because some
20
- languages require it.
21
-
22
20
  The `br_spaces` parameter allows an exception to this rule for a specific number
23
21
  of trailing spaces, typically used to insert an explicit line break. The default
24
- value allows 2 spaces to indicate a hard break (\<br> element).
22
+ value allows 2 spaces to indicate a hard break (\<br> element). (You must set
23
+ `br_spaces` to a value >= 2 for this parameter to take effect. Setting
24
+ `br_spaces` to 1 behaves the same as 0, disallowing any trailing spaces.)
25
25
 
26
- Note: You must set `br_spaces` to a value >= 2 for this parameter to take
27
- effect. Setting `br_spaces` to 1 behaves the same as 0, disallowing any trailing
28
- spaces.
26
+ By default, trailing space is allowed in indented and fenced code blocks because
27
+ some programming languages require that. To report such instances, set the
28
+ `code_blocks` parameter to `true`.
29
29
 
30
30
  By default, this rule will not trigger when the allowed number of spaces is
31
31
  used, even when it doesn't create a hard break (for example, at the end of a
32
- paragraph). To report such instances as well, set the `strict` parameter to
33
- `true`.
32
+ paragraph). To report such instances, set the `strict` parameter to `true`.
34
33
 
35
34
  ```markdown
36
35
  Text text text
package/doc/md010.md CHANGED
@@ -54,3 +54,9 @@ parameter to the desired value.
54
54
 
55
55
  Rationale: Hard tabs are often rendered inconsistently by different editors and
56
56
  can be harder to work with than spaces.
57
+
58
+ More information:
59
+
60
+ - <https://agiletribe.wordpress.com/2011/10/27/18-dont-use-tab-characters/>
61
+ - <https://www.jwz.org/doc/tabs-vs-spaces.html>
62
+ - <https://adamspiers.org/computing/why_no_tabs.html>
package/doc/md029.md CHANGED
@@ -9,6 +9,8 @@ Parameters:
9
9
  - `style`: List style (`string`, default `one_or_ordered`, values `one` /
10
10
  `one_or_ordered` / `ordered` / `zero`)
11
11
 
12
+ Fixable: Some violations can be fixed by tooling
13
+
12
14
  This rule is triggered for ordered lists that do not either start with '1.' or
13
15
  do not have a prefix that increases in numerical order (depending on the
14
16
  configured style). The less-common pattern of using '0.' as a first prefix or
package/doc/md033.md CHANGED
@@ -7,6 +7,8 @@ Aliases: `no-inline-html`
7
7
  Parameters:
8
8
 
9
9
  - `allowed_elements`: Allowed elements (`string[]`, default `[]`)
10
+ - `table_allowed_elements`: Allowed elements in tables (`string[]`, default
11
+ `[]`)
10
12
 
11
13
  This rule is triggered whenever raw HTML is used in a Markdown document:
12
14
 
@@ -20,7 +22,11 @@ To fix this, use 'pure' Markdown instead of including raw HTML:
20
22
  # Markdown heading
21
23
  ```
22
24
 
23
- Note: To allow specific HTML elements, use the `allowed_elements` parameter.
25
+ To allow specific HTML elements anywhere in Markdown content, set the
26
+ `allowed_elements` parameter to a list of HTML element names. To allow a
27
+ specific set of HTML elements within Markdown tables, set the
28
+ `table_allowed_elements` parameter to a list of HTML element names. This can be
29
+ used to permit the use of `<br>`-style line breaks only within Markdown tables.
24
30
 
25
31
  Rationale: Raw HTML is allowed in Markdown, but this rule is included for
26
32
  those who want their documents to only include "pure" Markdown, or for those
package/doc/md051.md CHANGED
@@ -100,13 +100,18 @@ This makes it easy to link directly to different sections within a document.
100
100
  However, section links change if headings are renamed or removed. This rule
101
101
  helps identify broken section links within a document.
102
102
 
103
- Section links are **not** part of the CommonMark specification. This rule
104
- enforces the [GitHub heading algorithm][github-heading-algorithm] which is:
105
- convert heading to lowercase, remove punctuation, convert spaces to dashes,
106
- append an incrementing integer as needed for uniqueness.
103
+ Note: Section links are **not** part of the CommonMark specification; this rule
104
+ enforces the [GitHub heading algorithm][github-heading-algorithm]:
107
105
 
106
+ 1. Convert text to lowercase
107
+ 2. Remove punctuation characters
108
+ 3. Convert spaces to dashes
109
+ 4. Append an incrementing integer (as needed for uniqueness)
110
+ 5. [URI-encode][encodeURIComponent] the result
111
+
112
+ [encodeURIComponent]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
108
113
  [github-section-links]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links
109
114
  [github-heading-algorithm]: https://github.com/gjtorikian/html-pipeline/blob/f13a1534cb650ba17af400d1acd3a22c28004c09/lib/html/pipeline/toc_filter.rb
110
- [github-linking-to-content]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-a-permanent-link-to-a-code-snippet#linking-to-markdown#linking-to-markdown
115
+ [github-linking-to-content]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-a-permanent-link-to-a-code-snippet#linking-to-markdown
111
116
  [html-top-fragment]: https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment
112
117
  [RegEx]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions
package/doc/md055.md CHANGED
@@ -52,4 +52,4 @@ Rationale: Some parsers have difficulty with tables that are missing their
52
52
  leading or trailing pipe characters. The use of leading/trailing pipes can also
53
53
  help provide visual clarity.
54
54
 
55
- [gfm-table-055]: https://github.github.com/gfm/#tables-extension-
55
+ [gfm-table-055]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables
package/doc/md056.md CHANGED
@@ -34,4 +34,4 @@ of cells or it will not be recognized as a table (per specification).
34
34
  Rationale: Extra cells in a row are usually not shown, so their data is lost.
35
35
  Missing cells in a row create holes in the table and suggest an omission.
36
36
 
37
- [gfm-table-056]: https://github.github.com/gfm/#tables-extension-
37
+ [gfm-table-056]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables
package/doc/md059.md CHANGED
@@ -27,4 +27,7 @@ translations for every language.
27
27
 
28
28
  Note: This rule checks Markdown links; HTML links are ignored.
29
29
 
30
- More information: <https://webaim.org/techniques/hypertext/>
30
+ More information:
31
+
32
+ - <https://webaim.org/techniques/hypertext/>
33
+ - <https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-link-only.html>
package/doc/md060.md ADDED
@@ -0,0 +1,118 @@
1
+ # `MD060` - Table column style
2
+
3
+ Tags: `table`
4
+
5
+ Aliases: `table-column-style`
6
+
7
+ Parameters:
8
+
9
+ - `aligned_delimiter`: Aligned delimiter columns (`boolean`, default `false`)
10
+ - `style`: Table column style (`string`, default `any`, values `aligned` /
11
+ `any` / `compact` / `tight`)
12
+
13
+ This rule is triggered when the column separator pipe characters (`|`) of a
14
+ [GitHub Flavored Markdown table][gfm-table-060] are used inconsistently.
15
+
16
+ This rule recognizes three table column styles based on popular use.
17
+
18
+ Style `aligned` ensures pipe characters are vertically aligned:
19
+
20
+ ```markdown
21
+ | Character | Meaning |
22
+ | --------- | ------- |
23
+ | Y | Yes |
24
+ | N | No |
25
+ ```
26
+
27
+ The `aligned` style ignores cell content, so the following is also valid:
28
+
29
+ ```markdown
30
+ | Character | Meaning |
31
+ |-----------|---------|
32
+ | Y | Yes |
33
+ | N | No |
34
+ ```
35
+
36
+ Style `compact` avoids extra padding with a single space around cell content:
37
+
38
+ ```markdown
39
+ | Character | Meaning |
40
+ | --- | --- |
41
+ | Y | Yes |
42
+ | N | No |
43
+ ```
44
+
45
+ Style `tight` uses no padding at all for cell content:
46
+
47
+ ```markdown
48
+ |Character|Meaning|
49
+ |---|---|
50
+ |Y|Yes|
51
+ |N|No|
52
+ ```
53
+
54
+ When this rule's `style` parameter is set to `aligned`, `compact`, or `tight`,
55
+ every table must match the corresponding pattern and any violations will be
56
+ reported. By default, or when the `any` style is used, each table is analyzed to
57
+ see if it satisfies any supported style. If so, no violations are reported. If
58
+ not, violations are be reported for whichever style would produce the *fewest*
59
+ issues (i.e., whichever style is the closest match).
60
+
61
+ Setting the `aligned_delimiter` parameter to `true` requires pipe characters in
62
+ the delimiter row to align with those in the header row. This can be used with
63
+ `compact` and `tight` tables to make the header text more obvious. (It's already
64
+ required for tables with style `aligned`.)
65
+
66
+ Style `compact` with `aligned_delimiter`:
67
+
68
+ ```markdown
69
+ | Character | Meaning |
70
+ | --------- | ------- |
71
+ | Y | Yes |
72
+ | N | No |
73
+ ```
74
+
75
+ Style `tight` with `aligned_delimiter`:
76
+
77
+ ```markdown
78
+ |Character|Meaning|
79
+ |---------|-------|
80
+ |Y|Yes|
81
+ |N|No|
82
+ ```
83
+
84
+ **Note**: This rule does not require leading/trailing pipe characters, so this
85
+ is also a valid table for style `compact`:
86
+
87
+ ```markdown
88
+ Character | Meaning
89
+ --- | ---
90
+ Y | Yes
91
+ N | No
92
+ ```
93
+
94
+ **Note**: Pipe alignment for the `aligned` style is based on visual appearance
95
+ and not character count. Because editors typically render [emoji][emoji] and
96
+ [CJK characters][cjk-characters] at *twice* the width of
97
+ [Latin characters][latin-script], this rule takes that into account for tables
98
+ using the `aligned` style. The following table is correctly formatted and will
99
+ appear aligned in most editors and monospaced fonts:
100
+
101
+ <!-- markdownlint-capture -->
102
+ <!-- markdownlint-disable extended-ascii -->
103
+
104
+ ```markdown
105
+ | Response | Emoji |
106
+ | -------- | ----- |
107
+ | Yes | ✅ |
108
+ | No | ❎ |
109
+ ```
110
+
111
+ <!-- markdownlint-restore -->
112
+
113
+ Rationale: Consistent formatting makes it easier to understand a document.
114
+
115
+ [cjk-characters]: https://en.wikipedia.org/wiki/CJK_characters
116
+ [emoji]: https://en.wikipedia.org/wiki/Emoji
117
+ [gfm-table-060]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables
118
+ [latin-script]: https://en.wikipedia.org/wiki/Latin_script
package/helpers/README.md CHANGED
@@ -22,8 +22,8 @@ be useful to custom rule authors and may avoid duplicating code.
22
22
  *None* - The entire body of code is tested to 100% coverage by the core
23
23
  `markdownlint` project, so there are no additional tests here.
24
24
 
25
- [custom-rules]: https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/CustomRules.md
25
+ [custom-rules]: https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/CustomRules.md
26
26
  [jsdoc]: https://en.m.wikipedia.org/wiki/JSDoc
27
27
  [markdown]: https://en.wikipedia.org/wiki/Markdown
28
28
  [markdownlint]: https://github.com/DavidAnson/markdownlint
29
- [rules]: https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/Rules.md
29
+ [rules]: https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/Rules.md
@@ -111,10 +111,11 @@ module.exports.cloneIfArray = cloneIfArray;
111
111
  /**
112
112
  * Clones the input if it is a URL.
113
113
  *
114
- * @param {Object} url Object of unknown type.
114
+ * @param {Object | undefined} url Object of unknown type.
115
115
  * @returns {Object} Clone of obj iff obj is a URL.
116
116
  */
117
117
  function cloneIfUrl(url) {
118
+ // @ts-ignore
118
119
  return isUrl(url) ? new URL(url) : url;
119
120
  }
120
121
  module.exports.cloneIfUrl = cloneIfUrl;
@@ -139,7 +140,7 @@ module.exports.getHtmlAttributeRe = function getHtmlAttributeRe(name) {
139
140
  function isBlankLine(line) {
140
141
  const startComment = "<!--";
141
142
  const endComment = "-->";
142
- const removeComments = (s) => {
143
+ const removeComments = (/** @type {string} */ s) => {
143
144
  while (true) {
144
145
  const start = s.indexOf(startComment);
145
146
  const end = s.indexOf(endComment);
@@ -177,8 +178,8 @@ const startsWithPipeRe = /^ *\|/;
177
178
  const notCrLfRe = /[^\r\n]/g;
178
179
  const notSpaceCrLfRe = /[^ \r\n]/g;
179
180
  const trailingSpaceRe = / +[\r\n]/g;
180
- const replaceTrailingSpace = (s) => s.replace(notCrLfRe, safeCommentCharacter);
181
- module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
181
+ const replaceTrailingSpace = (/** @type {string} */ s) => s.replace(notCrLfRe, safeCommentCharacter);
182
+ module.exports.clearHtmlCommentText = function clearHtmlCommentText(/** @type {string} */ text) {
182
183
  let i = 0;
183
184
  while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) {
184
185
  const j = text.indexOf(htmlCommentEnd, i + 2);
@@ -220,7 +221,7 @@ module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
220
221
  };
221
222
 
222
223
  // Escapes a string for use in a RegExp
223
- module.exports.escapeForRegExp = function escapeForRegExp(str) {
224
+ module.exports.escapeForRegExp = function escapeForRegExp(/** @type {string} */ str) {
224
225
  return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
225
226
  };
226
227
 
@@ -308,8 +309,8 @@ module.exports.addErrorDetailIf = addErrorDetailIf;
308
309
  * @param {RuleOnErrorFixInfo} [fixInfo] RuleOnErrorFixInfo instance.
309
310
  * @returns {void}
310
311
  */
311
- function addErrorContext(
312
- onError, lineNumber, context, start, end, range, fixInfo) {
312
+ function addErrorContext(onError, lineNumber, context, start, end, range, fixInfo) {
313
+ // Normalize new line characters so Linux and Windows trim consistently
313
314
  context = ellipsify(context.replace(newLineRe, "\n"), start, end);
314
315
  addError(onError, lineNumber, undefined, context, range, fixInfo);
315
316
  }
@@ -355,7 +356,7 @@ module.exports.hasOverlap = function hasOverlap(rangeA, rangeB) {
355
356
 
356
357
  // Determines if the front matter includes a title
357
358
  module.exports.frontMatterHasTitle =
358
- function frontMatterHasTitle(frontMatterLines, frontMatterTitlePattern) {
359
+ function frontMatterHasTitle(/** @type {readonly string[]} */ frontMatterLines, /** @type {string} */ frontMatterTitlePattern) {
359
360
  const ignoreFrontMatter =
360
361
  (frontMatterTitlePattern !== undefined) && !frontMatterTitlePattern;
361
362
  const frontMatterTitleRe =
@@ -367,18 +368,31 @@ module.exports.frontMatterHasTitle =
367
368
  frontMatterLines.some((line) => frontMatterTitleRe.test(line));
368
369
  };
369
370
 
371
+ /**
372
+ * Result object for getReferenceLinkImageData.
373
+ *
374
+ * @typedef {Object} GetReferenceLinkImageDataResult
375
+ * @property {Map<string, number[][]>} references References.
376
+ * @property {Map<string, number[][]>} shortcuts Shortcuts.
377
+ * @property {Map<string, [number, string]>} definitions Definitions.
378
+ * @property {[string, number][]} duplicateDefinitions Duplicate definitions.
379
+ * @property {number[]} definitionLineIndices Definition line indices.
380
+ */
381
+
370
382
  /**
371
383
  * Returns an object with information about reference links and images.
372
384
  *
373
385
  * @param {MicromarkToken[]} tokens Micromark tokens.
374
- * @returns {Object} Reference link/image data.
386
+ * @returns {GetReferenceLinkImageDataResult} Reference link/image data.
375
387
  */
376
388
  function getReferenceLinkImageData(tokens) {
377
- const normalizeReference = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
378
- const getText = (t) => t?.children.filter((c) => c.type !== "blockQuotePrefix").map((c) => c.text).join("");
389
+ const normalizeReference = (/** @type {string} */ s) => s.toLowerCase().trim().replace(/\s+/g, " ");
390
+ const getText = (/** @type {MicromarkToken} */ t) => t?.children.filter((c) => c.type !== "blockQuotePrefix").map((c) => c.text).join("");
391
+ /** @type {Map<string, number[][]>} */
379
392
  const references = new Map();
393
+ /** @type {Map<string, number[][]>} */
380
394
  const shortcuts = new Map();
381
- const addReferenceToDictionary = (token, label, isShortcut) => {
395
+ const addReferenceToDictionary = (/** @type {MicromarkToken} */ token, /** @type {string} */ label, /** @type {boolean} */ isShortcut) => {
382
396
  const referenceDatum = [
383
397
  token.startLine - 1,
384
398
  token.startColumn - 1,
@@ -390,8 +404,11 @@ function getReferenceLinkImageData(tokens) {
390
404
  referenceData.push(referenceDatum);
391
405
  dictionary.set(reference, referenceData);
392
406
  };
407
+ /** @type {Map<string, [number, string]>} */
393
408
  const definitions = new Map();
409
+ /** @type {number[]} */
394
410
  const definitionLineIndices = [];
411
+ /** @type {[string, number][]} */
395
412
  const duplicateDefinitions = [];
396
413
  const filteredTokens =
397
414
  micromark.filterByTypes(
@@ -433,7 +450,7 @@ function getReferenceLinkImageData(tokens) {
433
450
  micromark.getDescendantsByType(parent, [ "definitionDestination", "definitionDestinationRaw", "definitionDestinationString" ])[0]?.text;
434
451
  definitions.set(
435
452
  reference,
436
- [ token.startLine - 1, destinationString ]
453
+ [ token.startLine - 1, destinationString || "" ]
437
454
  );
438
455
  }
439
456
  }
@@ -490,7 +507,7 @@ module.exports.getReferenceLinkImageData = getReferenceLinkImageData;
490
507
  * Gets the most common line ending, falling back to the platform default.
491
508
  *
492
509
  * @param {string} input Markdown content to analyze.
493
- * @param {Object} [os] Node.js "os" module.
510
+ * @param {{EOL: string}} [os] Node.js "os" module.
494
511
  * @returns {string} Preferred line ending.
495
512
  */
496
513
  function getPreferredLineEnding(input, os) {
@@ -530,7 +547,7 @@ module.exports.getPreferredLineEnding = getPreferredLineEnding;
530
547
  * Expands a path with a tilde to an absolute path.
531
548
  *
532
549
  * @param {string} file Path that may begin with a tilde.
533
- * @param {Object} os Node.js "os" module.
550
+ * @param {{homedir: () => string}} os Node.js "os" module.
534
551
  * @returns {string} Absolute path (or original path).
535
552
  */
536
553
  function expandTildePath(file, os) {
@@ -538,3 +555,142 @@ function expandTildePath(file, os) {
538
555
  return homedir ? file.replace(/^~($|\/|\\)/, `${homedir}$1`) : file;
539
556
  }
540
557
  module.exports.expandTildePath = expandTildePath;
558
+
559
+ /** @typedef {import("../lib/markdownlint.mjs").LintError[]} LintErrors */
560
+ /** @typedef {import("../lib/markdownlint.mjs").LintResults} LintResults */
561
+
562
+ /**
563
+ * Converts lint errors from resultVersion 3 to 2.
564
+ *
565
+ * @param {LintErrors} errors Lint errors (v3).
566
+ * @returns {LintErrors} Lint errors (v2).
567
+ */
568
+ function convertLintErrorsVersion3To2(errors) {
569
+ const noPrevious = {
570
+ "ruleNames": [],
571
+ "lineNumber": -1
572
+ };
573
+ return errors.filter((error, index, array) => {
574
+ // @ts-ignore
575
+ delete error.fixInfo;
576
+ // @ts-ignore
577
+ delete error.severity;
578
+ const previous = array[index - 1] || noPrevious;
579
+ return (
580
+ (error.ruleNames[0] !== previous.ruleNames[0]) ||
581
+ (error.lineNumber !== previous.lineNumber)
582
+ );
583
+ });
584
+ }
585
+
586
+ /**
587
+ * Converts lint errors from resultVersion 2 to 1.
588
+ *
589
+ * @param {LintErrors} errors Lint errors (v2).
590
+ * @returns {LintErrors} Lint errors (v1).
591
+ */
592
+ function convertLintErrorsVersion2To1(errors) {
593
+ for (const error of errors) {
594
+ // @ts-ignore
595
+ error.ruleName = error.ruleNames[0];
596
+ // @ts-ignore
597
+ error.ruleAlias = error.ruleNames[1] || error.ruleName;
598
+ // @ts-ignore
599
+ delete error.ruleNames;
600
+ }
601
+ return errors;
602
+ }
603
+
604
+ /**
605
+ * Converts lint errors from resultVersion 2 to 0.
606
+ *
607
+ * @param {LintErrors} errors Lint errors (v2).
608
+ * @returns {LintErrors} Lint errors (v0).
609
+ */
610
+ function convertLintErrorsVersion2To0(errors) {
611
+ /** @type {Object.<string, number[]>} */
612
+ const dictionary = {};
613
+ for (const error of errors) {
614
+ const ruleName = error.ruleNames[0];
615
+ const ruleLines = dictionary[ruleName] || [];
616
+ ruleLines.push(error.lineNumber);
617
+ dictionary[ruleName] = ruleLines;
618
+ }
619
+ // @ts-ignore
620
+ return dictionary;
621
+ }
622
+
623
+ /**
624
+ * Copies and transforms lint results from resultVersion 3 to ?.
625
+ *
626
+ * @param {LintResults} results Lint results (v3).
627
+ * @param {(errors: LintErrors) => LintErrors} transform Lint errors (v?).
628
+ * @returns {LintResults} Lint results (v?).
629
+ */
630
+ function copyAndTransformResults(results, transform) {
631
+ /** @type {Object.<string, LintErrors>} */
632
+ const newResults = {};
633
+ Object.defineProperty(newResults, "toString", { "value": results.toString });
634
+ for (const key of Object.keys(results)) {
635
+ const arr = results[key].map((r) => ({ ...r }));
636
+ newResults[key] = transform(arr);
637
+ }
638
+ // @ts-ignore
639
+ return newResults;
640
+ }
641
+
642
+ /**
643
+ * Converts lint results from resultVersion 3 to 0.
644
+ *
645
+ * @param {LintResults} results Lint results (v3).
646
+ * @returns {LintResults} Lint results (v0).
647
+ */
648
+ module.exports.convertToResultVersion0 = function convertToResultVersion0(results) {
649
+ return copyAndTransformResults(results, (r) => convertLintErrorsVersion2To0(convertLintErrorsVersion3To2(r)));
650
+ };
651
+
652
+ /**
653
+ * Converts lint results from resultVersion 3 to 1.
654
+ *
655
+ * @param {LintResults} results Lint results (v3).
656
+ * @returns {LintResults} Lint results (v1).
657
+ */
658
+ module.exports.convertToResultVersion1 = function convertToResultVersion1(results) {
659
+ return copyAndTransformResults(results, (r) => convertLintErrorsVersion2To1(convertLintErrorsVersion3To2(r)));
660
+ };
661
+
662
+ /**
663
+ * Converts lint results from resultVersion 3 to 2.
664
+ *
665
+ * @param {LintResults} results Lint results (v3).
666
+ * @returns {LintResults} Lint results (v2).
667
+ */
668
+ module.exports.convertToResultVersion2 = function convertToResultVersion2(results) {
669
+ return copyAndTransformResults(results, convertLintErrorsVersion3To2);
670
+ };
671
+
672
+ /**
673
+ * Formats lint results to an array of strings.
674
+ *
675
+ * @param {LintResults|undefined} lintResults Lint results.
676
+ * @returns {string[]} Lint error strings.
677
+ */
678
+ module.exports.formatLintResults = function formatLintResults(lintResults) {
679
+ const results = [];
680
+ const entries = Object.entries(lintResults || {});
681
+ entries.sort((a, b) => a[0].localeCompare(b[0]));
682
+ for (const [ source, lintErrors ] of entries) {
683
+ for (const lintError of lintErrors) {
684
+ const { lineNumber, ruleNames, ruleDescription, errorDetail, errorContext, errorRange, severity } = lintError;
685
+ const rule = ruleNames.join("/");
686
+ const line = `:${lineNumber}`;
687
+ const rangeStart = (errorRange && errorRange[0]) || 0;
688
+ const column = rangeStart ? `:${rangeStart}` : "";
689
+ const description = ruleDescription;
690
+ const detail = (errorDetail ? ` [${errorDetail}]` : "");
691
+ const context = (errorContext ? ` [Context: "${errorContext}"]` : "");
692
+ results.push(`${source}${line}${column} ${severity} ${rule} ${description}${detail}${context}`);
693
+ }
694
+ }
695
+ return results;
696
+ };
@@ -2,7 +2,7 @@
2
2
 
3
3
  "use strict";
4
4
 
5
- const { flatTokensSymbol, htmlFlowSymbol } = require("./shared.cjs");
5
+ const { flatTokensSymbol, htmlFlowSymbol, newLineRe } = require("./shared.cjs");
6
6
 
7
7
  // eslint-disable-next-line jsdoc/valid-types
8
8
  /** @typedef {import("micromark-util-types", { with: { "resolution-mode": "import" } }).TokenType} TokenType */
@@ -17,6 +17,7 @@ const { flatTokensSymbol, htmlFlowSymbol } = require("./shared.cjs");
17
17
  * @returns {boolean} True iff the token is within an htmlFlow type.
18
18
  */
19
19
  function inHtmlFlow(token) {
20
+ // @ts-ignore
20
21
  return Boolean(token[htmlFlowSymbol]);
21
22
  }
22
23
 
@@ -126,8 +127,11 @@ function filterByPredicate(tokens, allowed, transformChildren) {
126
127
  * @returns {Token[]} Filtered tokens.
127
128
  */
128
129
  function filterByTypes(tokens, types, htmlFlow) {
129
- const predicate = (token) => types.includes(token.type) && (htmlFlow || !inHtmlFlow(token));
130
- const flatTokens = tokens[flatTokensSymbol];
130
+ const predicate = (/** @type {Token} */ token) => types.includes(token.type) && (htmlFlow || !inHtmlFlow(token));
131
+ /** @type {Token[]} */
132
+ const flatTokens =
133
+ // @ts-ignore
134
+ tokens[flatTokensSymbol];
131
135
  if (flatTokens) {
132
136
  return flatTokens.filter(predicate);
133
137
  }
@@ -163,7 +167,7 @@ function getBlockQuotePrefixText(tokens, lineNumber, count = 1) {
163
167
  function getDescendantsByType(parent, typePath) {
164
168
  let tokens = Array.isArray(parent) ? parent : [ parent ];
165
169
  for (const type of typePath) {
166
- const predicate = (token) => Array.isArray(type) ? type.includes(token.type) : (type === token.type);
170
+ const predicate = (/** @type {Token} */ token) => Array.isArray(type) ? type.includes(token.type) : (type === token.type);
167
171
  tokens = tokens.flatMap((t) => t.children.filter(predicate));
168
172
  }
169
173
  return tokens;
@@ -216,12 +220,11 @@ function getHeadingStyle(heading) {
216
220
  * @returns {string} Heading text.
217
221
  */
218
222
  function getHeadingText(heading) {
219
- const headingText = getDescendantsByType(heading, [ [ "atxHeadingText", "setextHeadingText" ] ])
223
+ return getDescendantsByType(heading, [ [ "atxHeadingText", "setextHeadingText" ] ])
220
224
  .flatMap((descendant) => descendant.children.filter((child) => child.type !== "htmlText"))
221
225
  .map((data) => data.text)
222
226
  .join("")
223
- .replace(/[\r\n]+/g, " ");
224
- return headingText || "";
227
+ .replace(newLineRe, " ");
225
228
  }
226
229
 
227
230
  /**
@@ -299,6 +302,7 @@ const nonContentTokens = new Set([
299
302
  "blockQuoteMarker",
300
303
  "blockQuotePrefix",
301
304
  "blockQuotePrefixWhitespace",
305
+ "gfmFootnoteDefinitionIndent",
302
306
  "lineEnding",
303
307
  "lineEndingBlank",
304
308
  "linePrefix",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdownlint-rule-helpers",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "A collection of markdownlint helper functions for custom rules",
5
5
  "main": "./helpers.cjs",
6
6
  "exports": {