markdownlint 0.26.1 → 0.27.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 (85) hide show
  1. package/CONTRIBUTING.md +81 -59
  2. package/README.md +424 -301
  3. package/demo/markdownlint-browser.js +445 -210
  4. package/doc/CustomRules.md +101 -50
  5. package/doc/Prettier.md +14 -10
  6. package/doc/ReleaseProcess.md +20 -0
  7. package/doc/Rules.md +464 -296
  8. package/doc/md001.md +37 -0
  9. package/doc/md002.md +37 -0
  10. package/doc/md003.md +59 -0
  11. package/doc/md004.md +49 -0
  12. package/doc/md005.md +53 -0
  13. package/doc/md006.md +46 -0
  14. package/doc/md007.md +55 -0
  15. package/doc/md009.md +51 -0
  16. package/doc/md010.md +56 -0
  17. package/doc/md011.md +30 -0
  18. package/doc/md012.md +38 -0
  19. package/doc/md013.md +60 -0
  20. package/doc/md014.md +54 -0
  21. package/doc/md018.md +27 -0
  22. package/doc/md019.md +28 -0
  23. package/doc/md020.md +29 -0
  24. package/doc/md021.md +31 -0
  25. package/doc/md022.md +46 -0
  26. package/doc/md023.md +26 -0
  27. package/doc/md024.md +47 -0
  28. package/doc/md025.md +49 -0
  29. package/doc/md026.md +41 -0
  30. package/doc/md027.md +24 -0
  31. package/doc/md028.md +40 -0
  32. package/doc/md029.md +98 -0
  33. package/doc/md030.md +82 -0
  34. package/doc/md031.md +50 -0
  35. package/doc/md032.md +38 -0
  36. package/doc/md033.md +27 -0
  37. package/doc/md034.md +44 -0
  38. package/doc/md035.md +42 -0
  39. package/doc/md036.md +45 -0
  40. package/doc/md037.md +37 -0
  41. package/doc/md038.md +40 -0
  42. package/doc/md039.md +21 -0
  43. package/doc/md040.md +52 -0
  44. package/doc/md041.md +49 -0
  45. package/doc/md042.md +32 -0
  46. package/doc/md043.md +72 -0
  47. package/doc/md044.md +35 -0
  48. package/doc/md045.md +34 -0
  49. package/doc/md046.md +40 -0
  50. package/doc/md047.md +34 -0
  51. package/doc/md048.md +41 -0
  52. package/doc/md049.md +32 -0
  53. package/doc/md050.md +32 -0
  54. package/doc/md051.md +50 -0
  55. package/doc/md052.md +33 -0
  56. package/doc/md053.md +38 -0
  57. package/helpers/README.md +35 -15
  58. package/helpers/helpers.js +234 -70
  59. package/helpers/package.json +2 -2
  60. package/lib/constants.js +8 -1
  61. package/lib/markdownlint.js +7 -12
  62. package/lib/md011.js +2 -2
  63. package/lib/md013.js +7 -6
  64. package/lib/md019.js +1 -1
  65. package/lib/md022.js +15 -3
  66. package/lib/md023.js +1 -1
  67. package/lib/md026.js +1 -1
  68. package/lib/md032.js +4 -5
  69. package/lib/md033.js +16 -5
  70. package/lib/md034.js +70 -45
  71. package/lib/md035.js +1 -1
  72. package/lib/md037.js +25 -22
  73. package/lib/md038.js +1 -1
  74. package/lib/md039.js +1 -1
  75. package/lib/md040.js +21 -2
  76. package/lib/md043.js +4 -2
  77. package/lib/md044.js +4 -3
  78. package/lib/md051.js +43 -11
  79. package/lib/md053.js +18 -11
  80. package/lib/rules.js +1 -1
  81. package/package.json +31 -21
  82. package/schema/.markdownlint.jsonc +16 -4
  83. package/schema/.markdownlint.yaml +13 -3
  84. package/schema/markdownlint-config-schema.json +45 -5
  85. 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/main/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/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
@@ -10,32 +10,35 @@ module.exports.newLineRe = newLineRe;
10
10
  // Regular expression for matching common front matter (YAML and TOML)
11
11
  module.exports.frontMatterRe =
12
12
  // eslint-disable-next-line max-len
13
- /((^---\s*$[^]*?^---\s*$)|(^\+\+\+\s*$[^]*?^(\+\+\+|\.\.\.)\s*$)|(^\{\s*$[^]*?^\}\s*$))(\r\n|\r|\n|$)/m;
13
+ /((^---\s*$[\s\S]*?^---\s*)|(^\+\+\+\s*$[\s\S]*?^(\+\+\+|\.\.\.)\s*)|(^\{\s*$[\s\S]*?^\}\s*))(\r\n|\r|\n|$)/m;
14
14
 
15
15
  // Regular expression for matching the start of inline disable/enable comments
16
16
  const inlineCommentStartRe =
17
17
  // 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;
18
+ /(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-line|disable-next-line|configure-file))(?:\s|-->)/gi;
19
19
  module.exports.inlineCommentStartRe = inlineCommentStartRe;
20
20
 
21
21
  // Regular expression for matching HTML elements
22
- const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^`>]*)?)\/?>/g;
22
+ const htmlElementRe = /<(([A-Za-z][A-Za-z\d-]*)(?:\s[^`>]*)?)\/?>/g;
23
23
  module.exports.htmlElementRe = htmlElementRe;
24
24
 
25
25
  // Regular expressions for range matching
26
- module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*(?:\/|[^\s\]"'\W])/ig;
27
26
  module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/;
28
27
  module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
29
28
 
30
29
  // Regular expression for all instances of emphasis markers
31
30
  const emphasisMarkersRe = /[_*]/g;
32
31
 
33
- // Regular expression for reference links (full and collapsed but not shortcut)
32
+ // Regular expression for blockquote prefixes
33
+ const blockquotePrefixRe = /^[>\s]*/;
34
+ module.exports.blockquotePrefixRe = blockquotePrefixRe;
35
+
36
+ // Regular expression for reference links (full, collapsed, and shortcut)
34
37
  const referenceLinkRe =
35
- /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|[^(]|$)/g;
38
+ /!?\\?\[((?:\[[^\]\0]*\]|[^[\]\0])*)\](?:\[([^\]\0]*)\]|([^(])|$)/g;
36
39
 
37
40
  // Regular expression for link reference definitions
38
- const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/;
41
+ const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])\]:/;
39
42
  module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe;
40
43
 
41
44
  // All punctuation characters (normal and full-width)
@@ -138,6 +141,12 @@ module.exports.includesSorted = function includesSorted(array, element) {
138
141
  // https://spec.commonmark.org/0.29/#html-comment
139
142
  const htmlCommentBegin = "<!--";
140
143
  const htmlCommentEnd = "-->";
144
+ const safeCommentCharacter = ".";
145
+ const startsWithPipeRe = /^ *\|/;
146
+ const notCrLfRe = /[^\r\n]/g;
147
+ const notSpaceCrLfRe = /[^ \r\n]/g;
148
+ const trailingSpaceRe = / +[\r\n]/g;
149
+ const replaceTrailingSpace = (s) => s.replace(notCrLfRe, safeCommentCharacter);
141
150
  module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
142
151
  let i = 0;
143
152
  while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) {
@@ -148,24 +157,30 @@ module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
148
157
  }
149
158
  // If the comment has content...
150
159
  if (j > i + htmlCommentBegin.length) {
151
- let k = i - 1;
152
- while (text[k] === " ") {
153
- k--;
154
- }
155
- // If comment is not within an indented code block...
156
- if (k >= i - 4) {
157
- const content = text.slice(i + htmlCommentBegin.length, j);
158
- const isBlock = (k < 0) || (text[k] === "\n");
159
- const isValid = isBlock ||
160
- (!content.startsWith(">") && !content.startsWith("->") &&
161
- !content.endsWith("-") && !content.includes("--"));
162
- // If a valid block/inline comment...
163
- if (isValid) {
164
- text =
165
- text.slice(0, i + htmlCommentBegin.length) +
166
- content.replace(/[^\r\n]/g, ".") +
167
- text.slice(j);
168
- }
160
+ const content = text.slice(i + htmlCommentBegin.length, j);
161
+ const lastLf = text.lastIndexOf("\n", i) + 1;
162
+ const preText = text.slice(lastLf, i);
163
+ const isBlock = preText.trim().length === 0;
164
+ const couldBeTable = startsWithPipeRe.test(preText);
165
+ const spansTableCells = couldBeTable && content.includes("\n");
166
+ const isValid =
167
+ isBlock ||
168
+ !(
169
+ spansTableCells ||
170
+ content.startsWith(">") ||
171
+ content.startsWith("->") ||
172
+ content.endsWith("-") ||
173
+ content.includes("--")
174
+ );
175
+ // If a valid block/inline comment...
176
+ if (isValid) {
177
+ const clearedContent = content
178
+ .replace(notSpaceCrLfRe, safeCommentCharacter)
179
+ .replace(trailingSpaceRe, replaceTrailingSpace);
180
+ text =
181
+ text.slice(0, i + htmlCommentBegin.length) +
182
+ clearedContent +
183
+ text.slice(j);
169
184
  }
170
185
  }
171
186
  i = j + htmlCommentEnd.length;
@@ -402,17 +417,22 @@ module.exports.flattenLists = function flattenLists(tokens) {
402
417
  return flattenedLists;
403
418
  };
404
419
 
405
- // Calls the provided function for each specified inline child token
406
- module.exports.forEachInlineChild =
420
+ /**
421
+ * Calls the provided function for each specified inline child token.
422
+ *
423
+ * @param {Object} params RuleParams instance.
424
+ * @param {string} type Token type identifier.
425
+ * @param {Function} handler Callback function.
426
+ * @returns {void}
427
+ */
407
428
  function forEachInlineChild(params, type, handler) {
408
- filterTokens(params, "inline", function forToken(token) {
409
- for (const child of token.children) {
410
- if (child.type === type) {
411
- handler(child, token);
412
- }
429
+ filterTokens(params, "inline", (token) => {
430
+ for (const child of token.children.filter((c) => c.type === type)) {
431
+ handler(child, token);
413
432
  }
414
433
  });
415
- };
434
+ }
435
+ module.exports.forEachInlineChild = forEachInlineChild;
416
436
 
417
437
  // Calls the provided function for each heading's content
418
438
  module.exports.forEachHeading = function forEachHeading(params, handler) {
@@ -592,6 +612,7 @@ module.exports.codeBlockAndSpanRanges = (params, lineMetadata) => {
592
612
  */
593
613
  module.exports.htmlElementRanges = (params, lineMetadata) => {
594
614
  const exclusions = [];
615
+ // Match with htmlElementRe
595
616
  forEachLine(lineMetadata, (line, lineIndex, inCode) => {
596
617
  let match = null;
597
618
  // eslint-disable-next-line no-unmodified-loop-condition
@@ -599,6 +620,31 @@ module.exports.htmlElementRanges = (params, lineMetadata) => {
599
620
  exclusions.push([ lineIndex, match.index, match[0].length ]);
600
621
  }
601
622
  });
623
+ // Match with html_inline
624
+ forEachInlineChild(params, "html_inline", (token, parent) => {
625
+ const parentContent = parent.content;
626
+ let tokenContent = token.content;
627
+ const parentIndex = parentContent.indexOf(tokenContent);
628
+ let deltaLines = 0;
629
+ let indent = 0;
630
+ for (let i = parentIndex - 1; i >= 0; i--) {
631
+ if (parentContent[i] === "\n") {
632
+ deltaLines++;
633
+ } else if (deltaLines === 0) {
634
+ indent++;
635
+ }
636
+ }
637
+ let lineIndex = token.lineNumber - 1 + deltaLines;
638
+ do {
639
+ const index = tokenContent.indexOf("\n");
640
+ const length = (index === -1) ? tokenContent.length : index;
641
+ exclusions.push([ lineIndex, indent, length ]);
642
+ tokenContent = tokenContent.slice(length + 1);
643
+ lineIndex++;
644
+ indent = 0;
645
+ } while (tokenContent.length > 0);
646
+ });
647
+ // Return results
602
648
  return exclusions;
603
649
  };
604
650
 
@@ -778,14 +824,15 @@ module.exports.emphasisMarkersInContent = emphasisMarkersInContent;
778
824
  function getReferenceLinkImageData(lineMetadata) {
779
825
  // Initialize return values
780
826
  const references = new Map();
781
- const shortcuts = new Set();
827
+ const shortcuts = new Map();
782
828
  const definitions = new Map();
783
829
  const duplicateDefinitions = [];
830
+ const definitionLineIndices = [];
784
831
  // Define helper functions
785
832
  const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
786
833
  const exclusions = [];
787
834
  const excluded = (match) => withinAnyRange(
788
- exclusions, 0, match.index, match[0].length
835
+ exclusions, 0, match.index, match[0].length - (match[3] || "").length
789
836
  );
790
837
  // Convert input to single-line so multi-line links/images are easier
791
838
  const lineOffsets = [];
@@ -794,6 +841,7 @@ function getReferenceLinkImageData(lineMetadata) {
794
841
  forEachLine(lineMetadata, (line, lineIndex, inCode) => {
795
842
  lineOffsets[lineIndex] = currentOffset;
796
843
  if (!inCode) {
844
+ line = line.replace(blockquotePrefixRe, "");
797
845
  if (line.trim().length === 0) {
798
846
  // Allow RegExp to detect the end of a block
799
847
  line = "\0";
@@ -819,7 +867,13 @@ function getReferenceLinkImageData(lineMetadata) {
819
867
  } else {
820
868
  definitions.set(label, lineIndex);
821
869
  }
822
- exclusions.push([ 0, lineOffsets[lineIndex], line.length ]);
870
+ const labelLength = linkReferenceDefinitionMatch[0].length;
871
+ exclusions.push([ 0, lineOffsets[lineIndex], labelLength ]);
872
+ const hasDefinition = line.slice(labelLength).trim().length > 0;
873
+ definitionLineIndices.push(lineIndex);
874
+ if (!hasDefinition) {
875
+ definitionLineIndices.push(lineIndex + 1);
876
+ }
823
877
  }
824
878
  }
825
879
  });
@@ -845,8 +899,7 @@ function getReferenceLinkImageData(lineMetadata) {
845
899
  !matchString.startsWith("!\\") &&
846
900
  !matchText.endsWith("\\") &&
847
901
  !(matchLabel || "").endsWith("\\") &&
848
- (topLevel || matchString.startsWith("!")) &&
849
- !excluded(referenceLinkMatch)
902
+ !(topLevel && excluded(referenceLinkMatch))
850
903
  ) {
851
904
  const shortcutLink = (matchLabel === undefined);
852
905
  const collapsedLink =
@@ -855,41 +908,44 @@ function getReferenceLinkImageData(lineMetadata) {
855
908
  (shortcutLink || collapsedLink) ? matchText : matchLabel
856
909
  );
857
910
  if (label.length > 0) {
911
+ const referenceindex = referenceLinkMatch.index;
912
+ if (topLevel) {
913
+ // Calculate line index
914
+ while (lineOffsets[lineIndex + 1] <= referenceindex) {
915
+ lineIndex++;
916
+ }
917
+ } else {
918
+ // Use provided line index
919
+ lineIndex = contentLineIndex;
920
+ }
921
+ const referenceIndex = referenceindex +
922
+ (topLevel ? -lineOffsets[lineIndex] : contentIndex);
923
+ const referenceDatum = [
924
+ lineIndex,
925
+ referenceIndex,
926
+ matchString.length,
927
+ matchText.length,
928
+ (matchLabel || "").length
929
+ ];
858
930
  if (shortcutLink) {
859
- // Track, but don't validate due to ambiguity: "text [text] text"
860
- shortcuts.add(label);
931
+ // Track separately due to ambiguity in "text [text] text"
932
+ const shortcutData = shortcuts.get(label) || [];
933
+ shortcutData.push(referenceDatum);
934
+ shortcuts.set(label, shortcutData);
861
935
  } else {
862
- const referenceindex = referenceLinkMatch.index;
863
- if (topLevel) {
864
- // Calculate line index
865
- while (lineOffsets[lineIndex + 1] <= referenceindex) {
866
- lineIndex++;
867
- }
868
- } else {
869
- // Use provided line index
870
- lineIndex = contentLineIndex;
871
- }
872
- const referenceIndex = referenceindex +
873
- (topLevel ? -lineOffsets[lineIndex] : contentIndex);
874
936
  // Track reference and location
875
937
  const referenceData = references.get(label) || [];
876
- referenceData.push([
877
- lineIndex,
878
- referenceIndex,
879
- matchString.length
880
- ]);
938
+ referenceData.push(referenceDatum);
881
939
  references.set(label, referenceData);
882
- // Check for images embedded in top-level link text
883
- if (!matchString.startsWith("!")) {
884
- pendingContents.push(
885
- {
886
- "content": matchText,
887
- "contentLineIndex": lineIndex,
888
- "contentIndex": referenceIndex + 1,
889
- "topLevel": false
890
- }
891
- );
892
- }
940
+ }
941
+ // Check for links embedded in brackets
942
+ if (!matchString.startsWith("!")) {
943
+ pendingContents.push({
944
+ "content": matchText,
945
+ "contentLineIndex": lineIndex,
946
+ "contentIndex": referenceIndex + 1,
947
+ "topLevel": false
948
+ });
893
949
  }
894
950
  }
895
951
  }
@@ -899,7 +955,8 @@ function getReferenceLinkImageData(lineMetadata) {
899
955
  references,
900
956
  shortcuts,
901
957
  definitions,
902
- duplicateDefinitions
958
+ duplicateDefinitions,
959
+ definitionLineIndices
903
960
  };
904
961
  }
905
962
  module.exports.getReferenceLinkImageData = getReferenceLinkImageData;
@@ -987,7 +1044,7 @@ module.exports.applyFix = applyFix;
987
1044
  * @returns {string} Corrected content.
988
1045
  */
989
1046
  function applyFixes(input, errors) {
990
- const lineEnding = getPreferredLineEnding(input, require("os"));
1047
+ const lineEnding = getPreferredLineEnding(input, require("node:os"));
991
1048
  const lines = input.split(newLineRe);
992
1049
  // Normalize fixInfo objects
993
1050
  let fixInfos = errors
@@ -1130,3 +1187,110 @@ function expandTildePath(file, os) {
1130
1187
  return homedir ? file.replace(/^~($|\/|\\)/, `${homedir}$1`) : file;
1131
1188
  }
1132
1189
  module.exports.expandTildePath = expandTildePath;
1190
+
1191
+ /**
1192
+ * RegExp.exec-style implementation of function expressions.
1193
+ *
1194
+ * @param {Function} funcExp Function that takes string and returns
1195
+ * [index, length] or null.
1196
+ * @param {string} input String to search.
1197
+ * @returns {string[] | null} RegExp.exec-style [match] with an index property.
1198
+ */
1199
+ function funcExpExec(funcExp, input) {
1200
+ // Start or resume match
1201
+ // @ts-ignore
1202
+ const lastIndex = funcExp.lastIndex || 0;
1203
+ const result = funcExp(input.slice(lastIndex));
1204
+ if (result) {
1205
+ // Update lastIndex and return match
1206
+ const [ subIndex, length ] = result;
1207
+ const index = lastIndex + subIndex;
1208
+ // @ts-ignore
1209
+ funcExp.lastIndex = index + length;
1210
+ const match = [ input.slice(index, index + length) ];
1211
+ // @ts-ignore
1212
+ match.index = index;
1213
+ return match;
1214
+ }
1215
+ // Reset lastIndex and return no match
1216
+ // @ts-ignore
1217
+ funcExp.lastIndex = 0;
1218
+ return null;
1219
+ }
1220
+ module.exports.funcExpExec = funcExpExec;
1221
+
1222
+ const urlFeProtocolRe = /(?:http|ftp)s?:\/\//i;
1223
+ const urlFeAutolinkTerminalsRe = / |$/;
1224
+ const urlFeBareTerminalsRe = /[ ,!`'"\]]|$/;
1225
+ const urlFeNonTerminalsRe = "-#/";
1226
+ const urlFePunctuationRe = /\p{Punctuation}/u;
1227
+ const urlFePrefixToPostfix = new Map([
1228
+ [ " ", " " ],
1229
+ [ "`", "`" ],
1230
+ [ "'", "'" ],
1231
+ [ "\"", "\"" ],
1232
+ [ "‘", "’" ],
1233
+ [ "“", "”" ],
1234
+ [ "«", "»" ],
1235
+ [ "*", "*" ],
1236
+ [ "_", "_" ],
1237
+ [ "(", ")" ],
1238
+ [ "[", "]" ],
1239
+ [ "{", "}" ],
1240
+ [ "<", ">" ],
1241
+ [ ">", "<" ]
1242
+ ]);
1243
+
1244
+ /**
1245
+ * Function expression that matches URLs.
1246
+ *
1247
+ * @param {string} input Substring to search for a URL.
1248
+ * @returns {Array | null} [index, length] of URL or null.
1249
+ */
1250
+ function urlFe(input) {
1251
+ // Find start of URL by searching for protocol
1252
+ const match = input.match(urlFeProtocolRe);
1253
+ if (match) {
1254
+ // Look for matching pre/postfix characters (ex: <...>)
1255
+ const start = match.index || 0;
1256
+ const length = match[0].length;
1257
+ const prefix = input[start - 1] || " ";
1258
+ const postfix = urlFePrefixToPostfix.get(prefix);
1259
+ // @ts-ignore
1260
+ let endPostfix = input.indexOf(postfix, start + length);
1261
+ if (endPostfix === -1) {
1262
+ endPostfix = input.length;
1263
+ }
1264
+ // Look for characters that terminate a URL
1265
+ const terminalsRe =
1266
+ (prefix === "<") ? urlFeAutolinkTerminalsRe : urlFeBareTerminalsRe;
1267
+ const endTerminal = start + input.slice(start).search(terminalsRe);
1268
+ // Determine tentative end of URL
1269
+ let end = Math.min(endPostfix, endTerminal);
1270
+ if (prefix === " ") {
1271
+ // If the URL used " " as pre/postfix characters, trim the end
1272
+ if (input[end - 1] === ")") {
1273
+ // Trim any ")" beyond the last "(...)" pair
1274
+ const lastOpenParen = input.lastIndexOf("(", end - 2);
1275
+ if (lastOpenParen <= start) {
1276
+ end--;
1277
+ } else {
1278
+ const nextCloseParen = input.indexOf(")", lastOpenParen + 1);
1279
+ end = nextCloseParen + 1;
1280
+ }
1281
+ } else {
1282
+ // Trim unwanted punctuation
1283
+ while (
1284
+ !urlFeNonTerminalsRe.includes(input[end - 1]) &&
1285
+ urlFePunctuationRe.test(input[end - 1])
1286
+ ) {
1287
+ end--;
1288
+ }
1289
+ }
1290
+ }
1291
+ return [ start, end - start ];
1292
+ }
1293
+ // No match
1294
+ return null;
1295
+ }
1296
+ module.exports.urlFe = urlFe;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdownlint-rule-helpers",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "description": "A collection of markdownlint helper functions for custom rules",
5
5
  "main": "./helpers.js",
6
6
  "exports": "./helpers.js",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "bugs": "https://github.com/DavidAnson/markdownlint/issues",
15
15
  "engines": {
16
- "node": ">=12"
16
+ "node": ">=14.18.0"
17
17
  },
18
18
  "keywords": [
19
19
  "markdownlint",
package/lib/constants.js CHANGED
@@ -3,5 +3,12 @@
3
3
  "use strict";
4
4
 
5
5
  module.exports.deprecatedRuleNames = [ "MD002", "MD006" ];
6
+ module.exports.fixableRuleNames = [
7
+ "MD004", "MD005", "MD006", "MD007", "MD009", "MD010",
8
+ "MD011", "MD012", "MD014", "MD018", "MD019", "MD020",
9
+ "MD021", "MD022", "MD023", "MD026", "MD027", "MD030",
10
+ "MD031", "MD032", "MD034", "MD037", "MD038", "MD039",
11
+ "MD044", "MD047", "MD049", "MD050", "MD051", "MD053"
12
+ ];
6
13
  module.exports.homepage = "https://github.com/DavidAnson/markdownlint";
7
- module.exports.version = "0.26.1";
14
+ module.exports.version = "0.27.0";