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.
- package/CHANGELOG.md +378 -0
- package/CONTRIBUTING.md +82 -59
- package/LICENSE +1 -1
- package/README.md +304 -305
- package/demo/markdownlint-browser.js +4973 -3379
- package/doc/CustomRules.md +99 -51
- package/doc/Prettier.md +14 -10
- package/doc/ReleaseProcess.md +20 -0
- package/doc/Rules.md +474 -311
- package/doc/md001.md +37 -0
- package/doc/md002.md +37 -0
- package/doc/md003.md +59 -0
- package/doc/md004.md +49 -0
- package/doc/md005.md +53 -0
- package/doc/md006.md +46 -0
- package/doc/md007.md +55 -0
- package/doc/md009.md +51 -0
- package/doc/md010.md +56 -0
- package/doc/md011.md +30 -0
- package/doc/md012.md +38 -0
- package/doc/md013.md +60 -0
- package/doc/md014.md +54 -0
- package/doc/md018.md +27 -0
- package/doc/md019.md +28 -0
- package/doc/md020.md +29 -0
- package/doc/md021.md +31 -0
- package/doc/md022.md +46 -0
- package/doc/md023.md +26 -0
- package/doc/md024.md +47 -0
- package/doc/md025.md +49 -0
- package/doc/md026.md +41 -0
- package/doc/md027.md +24 -0
- package/doc/md028.md +40 -0
- package/doc/md029.md +98 -0
- package/doc/md030.md +82 -0
- package/doc/md031.md +50 -0
- package/doc/md032.md +38 -0
- package/doc/md033.md +27 -0
- package/doc/md034.md +50 -0
- package/doc/md035.md +42 -0
- package/doc/md036.md +45 -0
- package/doc/md037.md +37 -0
- package/doc/md038.md +40 -0
- package/doc/md039.md +21 -0
- package/doc/md040.md +52 -0
- package/doc/md041.md +49 -0
- package/doc/md042.md +32 -0
- package/doc/md043.md +72 -0
- package/doc/md044.md +35 -0
- package/doc/md045.md +34 -0
- package/doc/md046.md +40 -0
- package/doc/md047.md +34 -0
- package/doc/md048.md +41 -0
- package/doc/md049.md +32 -0
- package/doc/md050.md +32 -0
- package/doc/md051.md +50 -0
- package/doc/md052.md +33 -0
- package/doc/md053.md +38 -0
- package/helpers/LICENSE +1 -1
- package/helpers/README.md +35 -15
- package/helpers/helpers.js +182 -162
- package/helpers/micromark.cjs +209 -0
- package/helpers/package.json +5 -2
- package/lib/constants.js +8 -1
- package/lib/markdownlint.d.ts +5 -5
- package/lib/markdownlint.js +46 -24
- package/lib/md002.js +1 -1
- package/lib/md011.js +2 -2
- package/lib/md013.js +7 -6
- package/lib/md019.js +1 -1
- package/lib/md022.js +15 -3
- package/lib/md023.js +1 -1
- package/lib/md026.js +1 -1
- package/lib/md027.js +1 -1
- package/lib/md028.js +1 -1
- package/lib/md033.js +47 -29
- package/lib/md034.js +54 -47
- package/lib/md035.js +9 -11
- package/lib/md036.js +1 -1
- package/lib/md037.js +2 -2
- package/lib/md038.js +81 -56
- package/lib/md039.js +1 -1
- package/lib/md040.js +21 -2
- package/lib/md041.js +1 -1
- package/lib/md043.js +4 -2
- package/lib/md044.js +58 -43
- package/lib/md046.js +1 -1
- package/lib/md048.js +3 -1
- package/lib/md051.js +37 -5
- package/lib/rules.js +1 -1
- package/package.json +43 -28
- package/schema/.markdownlint.jsonc +11 -4
- package/schema/.markdownlint.yaml +11 -6
- package/schema/markdownlint-config-schema.json +27 -3
- 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
package/helpers/README.md
CHANGED
|
@@ -4,23 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
The [Markdown]
|
|
8
|
-
[
|
|
9
|
-
[rules]
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
to be public, are not officially supported, and may
|
|
17
|
-
descriptive comments above each
|
|
18
|
-
annotations. That said, some of what's here will
|
|
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
|
-
##
|
|
20
|
+
## Examples
|
|
22
21
|
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/helpers/helpers.js
CHANGED
|
@@ -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*$[
|
|
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|-->)/
|
|
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-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
410
|
-
|
|
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",
|
|
413
|
-
for (const child of token.children) {
|
|
414
|
-
|
|
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}
|
|
806
|
+
* @param {Object} params RuleParams instance.
|
|
780
807
|
* @returns {Object} Reference link/image data.
|
|
781
808
|
*/
|
|
782
|
-
function getReferenceLinkImageData(
|
|
783
|
-
|
|
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
|
-
|
|
789
|
-
const
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
|
|
870
|
-
lineIndex = contentLineIndex;
|
|
848
|
+
definitions.set(reference, token.startLine - 1);
|
|
871
849
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
//
|
|
888
|
-
if (
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|