markdownlint 0.26.2 → 0.28.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 (95) hide show
  1. package/CHANGELOG.md +378 -0
  2. package/CONTRIBUTING.md +82 -59
  3. package/LICENSE +1 -1
  4. package/README.md +304 -305
  5. package/demo/markdownlint-browser.js +4973 -3379
  6. package/doc/CustomRules.md +99 -51
  7. package/doc/Prettier.md +14 -10
  8. package/doc/ReleaseProcess.md +20 -0
  9. package/doc/Rules.md +474 -311
  10. package/doc/md001.md +37 -0
  11. package/doc/md002.md +37 -0
  12. package/doc/md003.md +59 -0
  13. package/doc/md004.md +49 -0
  14. package/doc/md005.md +53 -0
  15. package/doc/md006.md +46 -0
  16. package/doc/md007.md +55 -0
  17. package/doc/md009.md +51 -0
  18. package/doc/md010.md +56 -0
  19. package/doc/md011.md +30 -0
  20. package/doc/md012.md +38 -0
  21. package/doc/md013.md +60 -0
  22. package/doc/md014.md +54 -0
  23. package/doc/md018.md +27 -0
  24. package/doc/md019.md +28 -0
  25. package/doc/md020.md +29 -0
  26. package/doc/md021.md +31 -0
  27. package/doc/md022.md +46 -0
  28. package/doc/md023.md +26 -0
  29. package/doc/md024.md +47 -0
  30. package/doc/md025.md +49 -0
  31. package/doc/md026.md +41 -0
  32. package/doc/md027.md +24 -0
  33. package/doc/md028.md +40 -0
  34. package/doc/md029.md +98 -0
  35. package/doc/md030.md +82 -0
  36. package/doc/md031.md +50 -0
  37. package/doc/md032.md +38 -0
  38. package/doc/md033.md +27 -0
  39. package/doc/md034.md +50 -0
  40. package/doc/md035.md +42 -0
  41. package/doc/md036.md +45 -0
  42. package/doc/md037.md +37 -0
  43. package/doc/md038.md +40 -0
  44. package/doc/md039.md +21 -0
  45. package/doc/md040.md +52 -0
  46. package/doc/md041.md +49 -0
  47. package/doc/md042.md +32 -0
  48. package/doc/md043.md +72 -0
  49. package/doc/md044.md +35 -0
  50. package/doc/md045.md +34 -0
  51. package/doc/md046.md +40 -0
  52. package/doc/md047.md +34 -0
  53. package/doc/md048.md +41 -0
  54. package/doc/md049.md +32 -0
  55. package/doc/md050.md +32 -0
  56. package/doc/md051.md +50 -0
  57. package/doc/md052.md +33 -0
  58. package/doc/md053.md +38 -0
  59. package/helpers/LICENSE +1 -1
  60. package/helpers/README.md +35 -15
  61. package/helpers/helpers.js +182 -162
  62. package/helpers/micromark.cjs +209 -0
  63. package/helpers/package.json +5 -2
  64. package/lib/constants.js +8 -1
  65. package/lib/markdownlint.d.ts +5 -5
  66. package/lib/markdownlint.js +46 -24
  67. package/lib/md002.js +1 -1
  68. package/lib/md011.js +2 -2
  69. package/lib/md013.js +7 -6
  70. package/lib/md019.js +1 -1
  71. package/lib/md022.js +15 -3
  72. package/lib/md023.js +1 -1
  73. package/lib/md026.js +1 -1
  74. package/lib/md027.js +1 -1
  75. package/lib/md028.js +1 -1
  76. package/lib/md033.js +47 -29
  77. package/lib/md034.js +54 -47
  78. package/lib/md035.js +9 -11
  79. package/lib/md036.js +1 -1
  80. package/lib/md037.js +2 -2
  81. package/lib/md038.js +81 -56
  82. package/lib/md039.js +1 -1
  83. package/lib/md040.js +21 -2
  84. package/lib/md041.js +1 -1
  85. package/lib/md043.js +4 -2
  86. package/lib/md044.js +58 -43
  87. package/lib/md046.js +1 -1
  88. package/lib/md048.js +3 -1
  89. package/lib/md051.js +37 -5
  90. package/lib/rules.js +1 -1
  91. package/package.json +43 -28
  92. package/schema/.markdownlint.jsonc +11 -4
  93. package/schema/.markdownlint.yaml +11 -6
  94. package/schema/markdownlint-config-schema.json +27 -3
  95. package/style/prettier.json +26 -0
package/doc/md051.md ADDED
@@ -0,0 +1,50 @@
1
+ # `MD051` - Link fragments should be valid
2
+
3
+ Tags: `links`
4
+
5
+ Aliases: `link-fragments`
6
+
7
+ Fixable: Some violations can be fixed by tooling
8
+
9
+ This rule is triggered when a link fragment does not match any of the fragments
10
+ that are automatically generated for headings in a document:
11
+
12
+ ```markdown
13
+ # Title
14
+
15
+ [Link](#fragment)
16
+ ```
17
+
18
+ To fix this issue, change the link fragment to reference an existing heading:
19
+
20
+ ```markdown
21
+ # Title
22
+
23
+ [Link](#title)
24
+ ```
25
+
26
+ Alternatively, an HTML `a` tag with an `id` or a `name` attribute can be used to
27
+ define a fragment:
28
+
29
+ ```markdown
30
+ <a id="bookmark"></a>
31
+
32
+ [Link](#bookmark)
33
+ ```
34
+
35
+ An `a` tag can be useful in scenarios where a heading is not appropriate or for
36
+ control over the text of the fragment identifier.
37
+
38
+ Rationale: [GitHub section links][github-section-links] are created
39
+ automatically for every heading when Markdown content is displayed on GitHub.
40
+ This makes it easy to link directly to different sections within a document.
41
+ However, section links change if headings are renamed or removed. This rule
42
+ helps identify broken section links within a document.
43
+
44
+ Section links are **not** part of the CommonMark specification. This rule
45
+ enforces the [GitHub heading algorithm][github-heading-algorithm] which is:
46
+ convert heading to lowercase, remove punctuation, convert spaces to dashes,
47
+ append an incrementing integer as needed for uniqueness.
48
+
49
+ [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
50
+ [github-heading-algorithm]: https://github.com/gjtorikian/html-pipeline/blob/f13a1534cb650ba17af400d1acd3a22c28004c09/lib/html/pipeline/toc_filter.rb
package/doc/md052.md ADDED
@@ -0,0 +1,33 @@
1
+ # `MD052` - Reference links and images should use a label that is defined
2
+
3
+ Tags: `images`, `links`
4
+
5
+ Aliases: `reference-links-images`
6
+
7
+ Links and images in Markdown can provide the link destination or image source
8
+ at the time of use or can define it elsewhere and use a label for reference.
9
+ The reference format is convenient for keeping paragraph text clutter-free
10
+ and makes it easy to reuse the same URL in multiple places.
11
+
12
+ There are three kinds of reference links and images:
13
+
14
+ ```markdown
15
+ Full: [text][label]
16
+ Collapsed: [label][]
17
+ Shortcut: [label]
18
+
19
+ Full: ![text][image]
20
+ Collapsed: ![image][]
21
+ Shortcut: ![image]
22
+
23
+ [label]: https://example.com/label
24
+ [image]: https://example.com/image
25
+ ```
26
+
27
+ A link or image renders correctly when a corresponding label is defined, but
28
+ the text displays with brackets if the label is not present. This rule warns
29
+ of undefined labels for "full" and "collapsed" reference syntax.
30
+
31
+ > "Shortcut" syntax is ambiguous and a missing label will not generate an
32
+ error. For example, `[shortcut]` could be a shortcut link or the text
33
+ "shortcut" in brackets.
package/doc/md053.md ADDED
@@ -0,0 +1,38 @@
1
+ # `MD053` - Link and image reference definitions should be needed
2
+
3
+ Tags: `images`, `links`
4
+
5
+ Aliases: `link-image-reference-definitions`
6
+
7
+ Parameters:
8
+
9
+ - `ignored_definitions`: Ignored definitions (`string[]`, default `["//"]`)
10
+
11
+ Fixable: Some violations can be fixed by tooling
12
+
13
+ Links and images in Markdown can provide the link destination or image source
14
+ at the time of use or can define it elsewhere and use a label for reference.
15
+ The reference format is convenient for keeping paragraph text clutter-free
16
+ and makes it easy to reuse the same URL in multiple places.
17
+
18
+ Because link and image reference definitions are located separately from
19
+ where they are used, there are two scenarios where a definition can be
20
+ unnecessary:
21
+
22
+ 1. If a label is not referenced by any link or image in a document, that
23
+ definition is unused and can be deleted.
24
+ 2. If a label is defined multiple times in a document, the first definition is
25
+ used and the others can be deleted.
26
+
27
+ This rule considers a reference definition to be used if any link or image
28
+ reference has the corresponding label. The "full", "collapsed", and "shortcut"
29
+ formats are all supported.
30
+
31
+ If there are reference definitions that are deliberately unreferenced, they can
32
+ be ignored by setting the `ignored_definitions` parameter. The default value of
33
+ this parameter ignores the following convention for adding non-HTML comments to
34
+ Markdown:
35
+
36
+ ```markdown
37
+ [//]: # (This behaves like a comment)
38
+ ```
package/helpers/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2022 David Anson
3
+ Copyright (c) David Anson
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/helpers/README.md CHANGED
@@ -4,23 +4,24 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- The [Markdown](https://en.wikipedia.org/wiki/Markdown) linter
8
- [`markdownlint`](https://github.com/DavidAnson/markdownlint) offers a variety of built-in validation
9
- [rules](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md) and supports the
10
- creation of [custom rules](https://github.com/DavidAnson/markdownlint/blob/main/doc/CustomRules.md).
11
- The internal rules share various helper functions; this package exposes those for reuse by custom rules.
7
+ The [Markdown][markdown] linter [`markdownlint`][markdownlint] offers a variety
8
+ of built-in validation [rules][rules] and supports the creation of [custom
9
+ rules][custom-rules]. The internal rules share various helper functions; this
10
+ package exposes those for reuse by custom rules.
12
11
 
13
12
  ## API
14
13
 
15
- _Undocumented_ - This package exports the internal functions as-is. The APIs were not originally meant
16
- to be public, are not officially supported, and may change from release to release. There are brief
17
- descriptive comments above each function, but no [JSDoc](https://en.m.wikipedia.org/wiki/JSDoc)
18
- annotations. That said, some of what's here will be useful to custom rule authors and may avoid
19
- duplicating code.
14
+ *Undocumented* - This package exports the internal functions as-is. The APIs
15
+ were not originally meant to be public, are not officially supported, and may
16
+ change from release to release. There are brief descriptive comments above each
17
+ function, but no [JSDoc][jsdoc] annotations. That said, some of what's here will
18
+ be useful to custom rule authors and may avoid duplicating code.
20
19
 
21
- ## Example
20
+ ## Examples
22
21
 
23
- ```js
22
+ ### Using Helpers from a Custom Rule
23
+
24
+ ```javascript
24
25
  const { forEachLine, getLineMetadata } = require("markdownlint-rule-helpers");
25
26
 
26
27
  module.exports = {
@@ -42,9 +43,28 @@ module.exports = {
42
43
  };
43
44
  ```
44
45
 
45
- See also: [`markdownlint` built-in rule implementations](https://github.com/DavidAnson/markdownlint/tree/main/lib).
46
+ ### Applying Recommended Fixes
47
+
48
+ ```javascript
49
+ const { "sync": markdownlintSync } = require("markdownlint");
50
+ const markdownlintRuleHelpers = require("markdownlint-rule-helpers");
51
+
52
+ function fixMarkdownlintViolations(content) {
53
+ const fixResults = markdownlintSync({ strings: { content } });
54
+ return markdownlintRuleHelpers.applyFixes(content, fixResults.content);
55
+ }
56
+ ```
57
+
58
+ See also: [`markdownlint` built-in rule implementations][lib].
46
59
 
47
60
  ## Tests
48
61
 
49
- _None_ - The entire body of code is tested to 100% coverage by the core `markdownlint` project,
50
- so there are no additional tests here.
62
+ *None* - The entire body of code is tested to 100% coverage by the core
63
+ `markdownlint` project, so there are no additional tests here.
64
+
65
+ [custom-rules]: https://github.com/DavidAnson/markdownlint/blob/main/doc/CustomRules.md
66
+ [jsdoc]: https://en.m.wikipedia.org/wiki/JSDoc
67
+ [lib]: https://github.com/DavidAnson/markdownlint/tree/main/lib
68
+ [markdown]: https://en.wikipedia.org/wiki/Markdown
69
+ [markdownlint]: https://github.com/DavidAnson/markdownlint
70
+ [rules]: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md
@@ -2,6 +2,8 @@
2
2
 
3
3
  "use strict";
4
4
 
5
+ const micromark = require("./micromark.cjs");
6
+
5
7
  // Regular expression for matching common newline characters
6
8
  // See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
7
9
  const newLineRe = /\r\n?|\n/g;
@@ -10,20 +12,19 @@ module.exports.newLineRe = newLineRe;
10
12
  // Regular expression for matching common front matter (YAML and TOML)
11
13
  module.exports.frontMatterRe =
12
14
  // eslint-disable-next-line max-len
13
- /((^---\s*$[^]*?^---\s*$)|(^\+\+\+\s*$[^]*?^(\+\+\+|\.\.\.)\s*$)|(^\{\s*$[^]*?^\}\s*$))(\r\n|\r|\n|$)/m;
15
+ /((^---\s*$[\s\S]*?^---\s*)|(^\+\+\+\s*$[\s\S]*?^(\+\+\+|\.\.\.)\s*)|(^\{\s*$[\s\S]*?^\}\s*))(\r\n|\r|\n|$)/m;
14
16
 
15
17
  // Regular expression for matching the start of inline disable/enable comments
16
18
  const inlineCommentStartRe =
17
19
  // eslint-disable-next-line max-len
18
- /(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-line|disable-next-line|configure-file))(?:\s|-->)/ig;
20
+ /(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-line|disable-next-line|configure-file))(?:\s|-->)/gi;
19
21
  module.exports.inlineCommentStartRe = inlineCommentStartRe;
20
22
 
21
23
  // Regular expression for matching HTML elements
22
- const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^`>]*)?)\/?>/g;
24
+ const htmlElementRe = /<(([A-Za-z][A-Za-z\d-]*)(?:\s[^`>]*)?)\/?>/g;
23
25
  module.exports.htmlElementRe = htmlElementRe;
24
26
 
25
27
  // Regular expressions for range matching
26
- module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*(?:\/|[^\s\]"'\W])/ig;
27
28
  module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/;
28
29
  module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
29
30
 
@@ -34,12 +35,8 @@ const emphasisMarkersRe = /[_*]/g;
34
35
  const blockquotePrefixRe = /^[>\s]*/;
35
36
  module.exports.blockquotePrefixRe = blockquotePrefixRe;
36
37
 
37
- // Regular expression for reference links (full, collapsed, and shortcut)
38
- const referenceLinkRe =
39
- /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|([^(])|$)/g;
40
-
41
38
  // Regular expression for link reference definitions
42
- const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/;
39
+ const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])\]:/;
43
40
  module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe;
44
41
 
45
42
  // All punctuation characters (normal and full-width)
@@ -142,6 +139,12 @@ module.exports.includesSorted = function includesSorted(array, element) {
142
139
  // https://spec.commonmark.org/0.29/#html-comment
143
140
  const htmlCommentBegin = "<!--";
144
141
  const htmlCommentEnd = "-->";
142
+ const safeCommentCharacter = ".";
143
+ const startsWithPipeRe = /^ *\|/;
144
+ const notCrLfRe = /[^\r\n]/g;
145
+ const notSpaceCrLfRe = /[^ \r\n]/g;
146
+ const trailingSpaceRe = / +[\r\n]/g;
147
+ const replaceTrailingSpace = (s) => s.replace(notCrLfRe, safeCommentCharacter);
145
148
  module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
146
149
  let i = 0;
147
150
  while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) {
@@ -152,24 +155,30 @@ module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
152
155
  }
153
156
  // If the comment has content...
154
157
  if (j > i + htmlCommentBegin.length) {
155
- let k = i - 1;
156
- while (text[k] === " ") {
157
- k--;
158
- }
159
- // If comment is not within an indented code block...
160
- if (k >= i - 4) {
161
- const content = text.slice(i + htmlCommentBegin.length, j);
162
- const isBlock = (k < 0) || (text[k] === "\n");
163
- const isValid = isBlock ||
164
- (!content.startsWith(">") && !content.startsWith("->") &&
165
- !content.endsWith("-") && !content.includes("--"));
166
- // If a valid block/inline comment...
167
- if (isValid) {
168
- text =
169
- text.slice(0, i + htmlCommentBegin.length) +
170
- content.replace(/[^\r\n]/g, ".") +
171
- text.slice(j);
172
- }
158
+ const content = text.slice(i + htmlCommentBegin.length, j);
159
+ const lastLf = text.lastIndexOf("\n", i) + 1;
160
+ const preText = text.slice(lastLf, i);
161
+ const isBlock = preText.trim().length === 0;
162
+ const couldBeTable = startsWithPipeRe.test(preText);
163
+ const spansTableCells = couldBeTable && content.includes("\n");
164
+ const isValid =
165
+ isBlock ||
166
+ !(
167
+ spansTableCells ||
168
+ content.startsWith(">") ||
169
+ content.startsWith("->") ||
170
+ content.endsWith("-") ||
171
+ content.includes("--")
172
+ );
173
+ // If a valid block/inline comment...
174
+ if (isValid) {
175
+ const clearedContent = content
176
+ .replace(notSpaceCrLfRe, safeCommentCharacter)
177
+ .replace(trailingSpaceRe, replaceTrailingSpace);
178
+ text =
179
+ text.slice(0, i + htmlCommentBegin.length) +
180
+ clearedContent +
181
+ text.slice(j);
173
182
  }
174
183
  }
175
184
  i = j + htmlCommentEnd.length;
@@ -182,19 +191,6 @@ module.exports.escapeForRegExp = function escapeForRegExp(str) {
182
191
  return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
183
192
  };
184
193
 
185
- // Un-escapes Markdown content (simple algorithm; not a parser)
186
- const escapedMarkdownRe = /\\./g;
187
- module.exports.unescapeMarkdown =
188
- function unescapeMarkdown(markdown, replacement) {
189
- return markdown.replace(escapedMarkdownRe, (match) => {
190
- const char = match[1];
191
- if ("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".includes(char)) {
192
- return replacement || char;
193
- }
194
- return match;
195
- });
196
- };
197
-
198
194
  /**
199
195
  * Return the string representation of a fence markup character.
200
196
  *
@@ -277,7 +273,7 @@ module.exports.unorderedListStyleFor = function unorderedListStyleFor(token) {
277
273
  * @returns {void}
278
274
  */
279
275
  function filterTokens(params, type, handler) {
280
- for (const token of params.tokens) {
276
+ for (const token of params.parsers.markdownit.tokens) {
281
277
  if (token.type === type) {
282
278
  handler(token);
283
279
  }
@@ -332,7 +328,7 @@ module.exports.getLineMetadata = function getLineMetadata(params) {
332
328
  filterTokens(params, "hr", (token) => {
333
329
  lineMetadata[token.map[0]][6] = true;
334
330
  });
335
- for (const token of params.tokens.filter(isMathBlock)) {
331
+ for (const token of params.parsers.markdownit.tokens.filter(isMathBlock)) {
336
332
  for (let i = token.map[0]; i < token.map[1]; i++) {
337
333
  lineMetadata[i][7] = true;
338
334
  }
@@ -406,22 +402,27 @@ module.exports.flattenLists = function flattenLists(tokens) {
406
402
  return flattenedLists;
407
403
  };
408
404
 
409
- // Calls the provided function for each specified inline child token
410
- module.exports.forEachInlineChild =
405
+ /**
406
+ * Calls the provided function for each specified inline child token.
407
+ *
408
+ * @param {Object} params RuleParams instance.
409
+ * @param {string} type Token type identifier.
410
+ * @param {Function} handler Callback function.
411
+ * @returns {void}
412
+ */
411
413
  function forEachInlineChild(params, type, handler) {
412
- filterTokens(params, "inline", function forToken(token) {
413
- for (const child of token.children) {
414
- if (child.type === type) {
415
- handler(child, token);
416
- }
414
+ filterTokens(params, "inline", (token) => {
415
+ for (const child of token.children.filter((c) => c.type === type)) {
416
+ handler(child, token);
417
417
  }
418
418
  });
419
- };
419
+ }
420
+ module.exports.forEachInlineChild = forEachInlineChild;
420
421
 
421
422
  // Calls the provided function for each heading's content
422
423
  module.exports.forEachHeading = function forEachHeading(params, handler) {
423
424
  let heading = null;
424
- for (const token of params.tokens) {
425
+ for (const token of params.parsers.markdownit.tokens) {
425
426
  if (token.type === "heading_open") {
426
427
  heading = token;
427
428
  } else if (token.type === "heading_close") {
@@ -596,6 +597,7 @@ module.exports.codeBlockAndSpanRanges = (params, lineMetadata) => {
596
597
  */
597
598
  module.exports.htmlElementRanges = (params, lineMetadata) => {
598
599
  const exclusions = [];
600
+ // Match with htmlElementRe
599
601
  forEachLine(lineMetadata, (line, lineIndex, inCode) => {
600
602
  let match = null;
601
603
  // eslint-disable-next-line no-unmodified-loop-condition
@@ -603,6 +605,31 @@ module.exports.htmlElementRanges = (params, lineMetadata) => {
603
605
  exclusions.push([ lineIndex, match.index, match[0].length ]);
604
606
  }
605
607
  });
608
+ // Match with html_inline
609
+ forEachInlineChild(params, "html_inline", (token, parent) => {
610
+ const parentContent = parent.content;
611
+ let tokenContent = token.content;
612
+ const parentIndex = parentContent.indexOf(tokenContent);
613
+ let deltaLines = 0;
614
+ let indent = 0;
615
+ for (let i = parentIndex - 1; i >= 0; i--) {
616
+ if (parentContent[i] === "\n") {
617
+ deltaLines++;
618
+ } else if (deltaLines === 0) {
619
+ indent++;
620
+ }
621
+ }
622
+ let lineIndex = token.lineNumber - 1 + deltaLines;
623
+ do {
624
+ const index = tokenContent.indexOf("\n");
625
+ const length = (index === -1) ? tokenContent.length : index;
626
+ exclusions.push([ lineIndex, indent, length ]);
627
+ tokenContent = tokenContent.slice(length + 1);
628
+ lineIndex++;
629
+ indent = 0;
630
+ } while (tokenContent.length > 0);
631
+ });
632
+ // Return results
606
633
  return exclusions;
607
634
  };
608
635
 
@@ -776,132 +803,125 @@ module.exports.emphasisMarkersInContent = emphasisMarkersInContent;
776
803
  /**
777
804
  * Returns an object with information about reference links and images.
778
805
  *
779
- * @param {Object} lineMetadata Line metadata object.
806
+ * @param {Object} params RuleParams instance.
780
807
  * @returns {Object} Reference link/image data.
781
808
  */
782
- function getReferenceLinkImageData(lineMetadata) {
783
- // Initialize return values
784
- const references = new Map();
785
- const shortcuts = new Set();
809
+ function getReferenceLinkImageData(params) {
810
+ const normalizeReference = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
786
811
  const definitions = new Map();
812
+ const definitionLineIndices = [];
787
813
  const duplicateDefinitions = [];
788
- // Define helper functions
789
- const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
790
- const exclusions = [];
791
- const excluded = (match) => withinAnyRange(
792
- exclusions, 0, match.index, match[0].length - (match[3] || "").length
793
- );
794
- // Convert input to single-line so multi-line links/images are easier
795
- const lineOffsets = [];
796
- let currentOffset = 0;
797
- const contentLines = [];
798
- forEachLine(lineMetadata, (line, lineIndex, inCode) => {
799
- lineOffsets[lineIndex] = currentOffset;
800
- if (!inCode) {
801
- line = line.replace(blockquotePrefixRe, "");
802
- if (line.trim().length === 0) {
803
- // Allow RegExp to detect the end of a block
804
- line = "\0";
805
- }
806
- contentLines.push(line);
807
- currentOffset += line.length + 1;
808
- }
809
- });
810
- lineOffsets.push(currentOffset);
811
- const contentLine = contentLines.join(" ");
812
- // Determine single-line exclusions for inline code spans
813
- forEachInlineCodeSpan(contentLine, (code, lineIndex, columnIndex) => {
814
- exclusions.push([ 0, columnIndex, code.length ]);
815
- });
816
- // Identify all link/image reference definitions
817
- forEachLine(lineMetadata, (line, lineIndex, inCode) => {
818
- if (!inCode) {
819
- const linkReferenceDefinitionMatch = linkReferenceDefinitionRe.exec(line);
820
- if (linkReferenceDefinitionMatch) {
821
- const label = normalizeLabel(linkReferenceDefinitionMatch[1]);
822
- if (definitions.has(label)) {
823
- duplicateDefinitions.push([ label, lineIndex ]);
824
- } else {
825
- definitions.set(label, lineIndex);
814
+ const references = new Map();
815
+ const shortcuts = new Map();
816
+ const filteredTokens =
817
+ micromark.filterByTypes(
818
+ params.parsers.micromark.tokens,
819
+ [
820
+ // definitionLineIndices
821
+ "definition", "gfmFootnoteDefinition",
822
+ // definitions and definitionLineIndices
823
+ "definitionLabelString", "gfmFootnoteDefinitionLabelString",
824
+ // references and shortcuts
825
+ "gfmFootnoteCall", "image", "link"
826
+ ]
827
+ );
828
+ for (const token of filteredTokens) {
829
+ let labelPrefix = "";
830
+ // eslint-disable-next-line default-case
831
+ switch (token.type) {
832
+ case "definition":
833
+ case "gfmFootnoteDefinition":
834
+ // definitionLineIndices
835
+ for (let i = token.startLine; i <= token.endLine; i++) {
836
+ definitionLineIndices.push(i - 1);
826
837
  }
827
- exclusions.push([ 0, lineOffsets[lineIndex], line.length ]);
828
- }
829
- }
830
- });
831
- // Identify all link and image references
832
- let lineIndex = 0;
833
- const pendingContents = [
834
- {
835
- "content": contentLine,
836
- "contentLineIndex": 0,
837
- "contentIndex": 0,
838
- "topLevel": true
839
- }
840
- ];
841
- let pendingContent = null;
842
- while ((pendingContent = pendingContents.shift())) {
843
- const { content, contentLineIndex, contentIndex, topLevel } =
844
- pendingContent;
845
- let referenceLinkMatch = null;
846
- while ((referenceLinkMatch = referenceLinkRe.exec(content)) !== null) {
847
- const [ matchString, matchText, matchLabel ] = referenceLinkMatch;
848
- if (
849
- !matchString.startsWith("\\") &&
850
- !matchString.startsWith("!\\") &&
851
- !matchText.endsWith("\\") &&
852
- !(matchLabel || "").endsWith("\\") &&
853
- !(topLevel && excluded(referenceLinkMatch))
854
- ) {
855
- const shortcutLink = (matchLabel === undefined);
856
- const collapsedLink =
857
- (!shortcutLink && (matchLabel.length === 0));
858
- const label = normalizeLabel(
859
- (shortcutLink || collapsedLink) ? matchText : matchLabel
860
- );
861
- if (label.length > 0) {
862
- const referenceindex = referenceLinkMatch.index;
863
- if (topLevel) {
864
- // Calculate line index
865
- while (lineOffsets[lineIndex + 1] <= referenceindex) {
866
- lineIndex++;
867
- }
838
+ break;
839
+ case "gfmFootnoteDefinitionLabelString":
840
+ labelPrefix = "^";
841
+ case "definitionLabelString": // eslint-disable-line no-fallthrough
842
+ {
843
+ // definitions and definitionLineIndices
844
+ const reference = normalizeReference(`${labelPrefix}${token.text}`);
845
+ if (definitions.has(reference)) {
846
+ duplicateDefinitions.push([ reference, token.startLine - 1 ]);
868
847
  } else {
869
- // Use provided line index
870
- lineIndex = contentLineIndex;
848
+ definitions.set(reference, token.startLine - 1);
871
849
  }
872
- const referenceIndex = referenceindex +
873
- (topLevel ? -lineOffsets[lineIndex] : contentIndex);
874
- if (shortcutLink) {
875
- // Track separately due to ambiguity in "text [text] text"
876
- shortcuts.add(label);
877
- } else {
878
- // Track reference and location
879
- const referenceData = references.get(label) || [];
880
- referenceData.push([
881
- lineIndex,
882
- referenceIndex,
883
- matchString.length
884
- ]);
885
- references.set(label, referenceData);
850
+ }
851
+ break;
852
+ case "gfmFootnoteCall":
853
+ case "image":
854
+ case "link":
855
+ {
856
+ let isShortcut = false;
857
+ let isFullOrCollapsed = false;
858
+ let labelText = null;
859
+ let referenceStringText = null;
860
+ const shortcutCandidate =
861
+ micromark.matchAndGetTokensByType(token.children, [ "label" ]);
862
+ if (shortcutCandidate) {
863
+ labelText =
864
+ micromark.getTokenTextByType(
865
+ shortcutCandidate[0].children, "labelText"
866
+ );
867
+ isShortcut = (labelText !== null);
868
+ }
869
+ const fullAndCollapsedCandidate =
870
+ micromark.matchAndGetTokensByType(
871
+ token.children, [ "label", "reference" ]
872
+ );
873
+ if (fullAndCollapsedCandidate) {
874
+ labelText =
875
+ micromark.getTokenTextByType(
876
+ fullAndCollapsedCandidate[0].children, "labelText"
877
+ );
878
+ referenceStringText =
879
+ micromark.getTokenTextByType(
880
+ fullAndCollapsedCandidate[1].children, "referenceString"
881
+ );
882
+ isFullOrCollapsed = (labelText !== null);
883
+ }
884
+ const footnote = micromark.matchAndGetTokensByType(
885
+ token.children,
886
+ [
887
+ "gfmFootnoteCallLabelMarker", "gfmFootnoteCallMarker",
888
+ "gfmFootnoteCallString", "gfmFootnoteCallLabelMarker"
889
+ ],
890
+ [ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ]
891
+ );
892
+ if (footnote) {
893
+ const callMarkerText = footnote[0].text;
894
+ const callString = footnote[1].text;
895
+ labelText = `${callMarkerText}${callString}`;
896
+ isShortcut = true;
886
897
  }
887
- // Check for links embedded in brackets
888
- if (!matchString.startsWith("!")) {
889
- pendingContents.push({
890
- "content": matchText,
891
- "contentLineIndex": lineIndex,
892
- "contentIndex": referenceIndex + 1,
893
- "topLevel": false
894
- });
898
+ // Track shortcuts separately due to ambiguity in "text [text] text"
899
+ if (isShortcut || isFullOrCollapsed) {
900
+ const referenceDatum = [
901
+ token.startLine - 1,
902
+ token.startColumn - 1,
903
+ token.text.length,
904
+ // @ts-ignore
905
+ labelText.length,
906
+ (referenceStringText || "").length
907
+ ];
908
+ const reference =
909
+ normalizeReference(referenceStringText || labelText);
910
+ const dictionary = isShortcut ? shortcuts : references;
911
+ const referenceData = dictionary.get(reference) || [];
912
+ referenceData.push(referenceDatum);
913
+ dictionary.set(reference, referenceData);
895
914
  }
896
915
  }
897
- }
916
+ break;
898
917
  }
899
918
  }
900
919
  return {
901
920
  references,
902
921
  shortcuts,
903
922
  definitions,
904
- duplicateDefinitions
923
+ duplicateDefinitions,
924
+ definitionLineIndices
905
925
  };
906
926
  }
907
927
  module.exports.getReferenceLinkImageData = getReferenceLinkImageData;
@@ -989,7 +1009,7 @@ module.exports.applyFix = applyFix;
989
1009
  * @returns {string} Corrected content.
990
1010
  */
991
1011
  function applyFixes(input, errors) {
992
- const lineEnding = getPreferredLineEnding(input, require("os"));
1012
+ const lineEnding = getPreferredLineEnding(input, require("node:os"));
993
1013
  const lines = input.split(newLineRe);
994
1014
  // Normalize fixInfo objects
995
1015
  let fixInfos = errors