Update MD053/link-image-reference-definitions to recognize links within square brackets (fixes #537).

This commit is contained in:
David Anson 2022-07-30 16:12:27 -07:00
parent 08cdd9513a
commit 48f47b5214
5 changed files with 117 additions and 69 deletions

View file

@ -52,8 +52,8 @@ module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/;
module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/; module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
// Regular expression for all instances of emphasis markers // Regular expression for all instances of emphasis markers
const emphasisMarkersRe = /[_*]/g; const emphasisMarkersRe = /[_*]/g;
// Regular expression for reference links (full and collapsed but not shortcut) // Regular expression for reference links (full, collapsed, and shortcut)
const referenceLinkRe = /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|[^(]|$)/g; const referenceLinkRe = /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|([^(])|$)/g;
// Regular expression for link reference definitions // Regular expression for link reference definitions
const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/; const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/;
module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe; module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe;
@ -755,7 +755,7 @@ function getReferenceLinkImageData(lineMetadata) {
// Define helper functions // Define helper functions
const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
const exclusions = []; const exclusions = [];
const excluded = (match) => withinAnyRange(exclusions, 0, match.index, match[0].length); const excluded = (match) => withinAnyRange(exclusions, 0, match.index, match[0].length - (match[3] || "").length);
// Convert input to single-line so multi-line links/images are easier // Convert input to single-line so multi-line links/images are easier
const lineOffsets = []; const lineOffsets = [];
let currentOffset = 0; let currentOffset = 0;
@ -813,30 +813,29 @@ function getReferenceLinkImageData(lineMetadata) {
!matchString.startsWith("!\\") && !matchString.startsWith("!\\") &&
!matchText.endsWith("\\") && !matchText.endsWith("\\") &&
!(matchLabel || "").endsWith("\\") && !(matchLabel || "").endsWith("\\") &&
(topLevel || matchString.startsWith("!")) && !(topLevel && excluded(referenceLinkMatch))) {
!excluded(referenceLinkMatch)) {
const shortcutLink = (matchLabel === undefined); const shortcutLink = (matchLabel === undefined);
const collapsedLink = (!shortcutLink && (matchLabel.length === 0)); const collapsedLink = (!shortcutLink && (matchLabel.length === 0));
const label = normalizeLabel((shortcutLink || collapsedLink) ? matchText : matchLabel); const label = normalizeLabel((shortcutLink || collapsedLink) ? matchText : matchLabel);
if (label.length > 0) { if (label.length > 0) {
const referenceindex = referenceLinkMatch.index;
if (topLevel) {
// Calculate line index
while (lineOffsets[lineIndex + 1] <= referenceindex) {
lineIndex++;
}
}
else {
// Use provided line index
lineIndex = contentLineIndex;
}
const referenceIndex = referenceindex +
(topLevel ? -lineOffsets[lineIndex] : contentIndex);
if (shortcutLink) { if (shortcutLink) {
// Track, but don't validate due to ambiguity: "text [text] text" // Track separately due to ambiguity in "text [text] text"
shortcuts.add(label); shortcuts.add(label);
} }
else { else {
const referenceindex = referenceLinkMatch.index;
if (topLevel) {
// Calculate line index
while (lineOffsets[lineIndex + 1] <= referenceindex) {
lineIndex++;
}
}
else {
// Use provided line index
lineIndex = contentLineIndex;
}
const referenceIndex = referenceindex +
(topLevel ? -lineOffsets[lineIndex] : contentIndex);
// Track reference and location // Track reference and location
const referenceData = references.get(label) || []; const referenceData = references.get(label) || [];
referenceData.push([ referenceData.push([
@ -845,15 +844,15 @@ function getReferenceLinkImageData(lineMetadata) {
matchString.length matchString.length
]); ]);
references.set(label, referenceData); references.set(label, referenceData);
// Check for images embedded in top-level link text }
if (!matchString.startsWith("!")) { // Check for links embedded in brackets
pendingContents.push({ if (!matchString.startsWith("!")) {
"content": matchText, pendingContents.push({
"contentLineIndex": lineIndex, "content": matchText,
"contentIndex": referenceIndex + 1, "contentLineIndex": lineIndex,
"topLevel": false "contentIndex": referenceIndex + 1,
}); "topLevel": false
} });
} }
} }
} }

View file

@ -30,9 +30,9 @@ module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
// Regular expression for all instances of emphasis markers // Regular expression for all instances of emphasis markers
const emphasisMarkersRe = /[_*]/g; const emphasisMarkersRe = /[_*]/g;
// Regular expression for reference links (full and collapsed but not shortcut) // Regular expression for reference links (full, collapsed, and shortcut)
const referenceLinkRe = const referenceLinkRe =
/!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|[^(]|$)/g; /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|([^(])|$)/g;
// Regular expression for link reference definitions // Regular expression for link reference definitions
const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/; const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/;
@ -785,7 +785,7 @@ function getReferenceLinkImageData(lineMetadata) {
const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
const exclusions = []; const exclusions = [];
const excluded = (match) => withinAnyRange( const excluded = (match) => withinAnyRange(
exclusions, 0, match.index, match[0].length exclusions, 0, match.index, match[0].length - (match[3] || "").length
); );
// Convert input to single-line so multi-line links/images are easier // Convert input to single-line so multi-line links/images are easier
const lineOffsets = []; const lineOffsets = [];
@ -845,8 +845,7 @@ function getReferenceLinkImageData(lineMetadata) {
!matchString.startsWith("!\\") && !matchString.startsWith("!\\") &&
!matchText.endsWith("\\") && !matchText.endsWith("\\") &&
!(matchLabel || "").endsWith("\\") && !(matchLabel || "").endsWith("\\") &&
(topLevel || matchString.startsWith("!")) && !(topLevel && excluded(referenceLinkMatch))
!excluded(referenceLinkMatch)
) { ) {
const shortcutLink = (matchLabel === undefined); const shortcutLink = (matchLabel === undefined);
const collapsedLink = const collapsedLink =
@ -855,22 +854,22 @@ function getReferenceLinkImageData(lineMetadata) {
(shortcutLink || collapsedLink) ? matchText : matchLabel (shortcutLink || collapsedLink) ? matchText : matchLabel
); );
if (label.length > 0) { if (label.length > 0) {
const referenceindex = referenceLinkMatch.index;
if (topLevel) {
// Calculate line index
while (lineOffsets[lineIndex + 1] <= referenceindex) {
lineIndex++;
}
} else {
// Use provided line index
lineIndex = contentLineIndex;
}
const referenceIndex = referenceindex +
(topLevel ? -lineOffsets[lineIndex] : contentIndex);
if (shortcutLink) { if (shortcutLink) {
// Track, but don't validate due to ambiguity: "text [text] text" // Track separately due to ambiguity in "text [text] text"
shortcuts.add(label); shortcuts.add(label);
} else { } else {
const referenceindex = referenceLinkMatch.index;
if (topLevel) {
// Calculate line index
while (lineOffsets[lineIndex + 1] <= referenceindex) {
lineIndex++;
}
} else {
// Use provided line index
lineIndex = contentLineIndex;
}
const referenceIndex = referenceindex +
(topLevel ? -lineOffsets[lineIndex] : contentIndex);
// Track reference and location // Track reference and location
const referenceData = references.get(label) || []; const referenceData = references.get(label) || [];
referenceData.push([ referenceData.push([
@ -879,17 +878,15 @@ function getReferenceLinkImageData(lineMetadata) {
matchString.length matchString.length
]); ]);
references.set(label, referenceData); references.set(label, referenceData);
// Check for images embedded in top-level link text }
if (!matchString.startsWith("!")) { // Check for links embedded in brackets
pendingContents.push( if (!matchString.startsWith("!")) {
{ pendingContents.push({
"content": matchText, "content": matchText,
"contentLineIndex": lineIndex, "contentLineIndex": lineIndex,
"contentIndex": referenceIndex + 1, "contentIndex": referenceIndex + 1,
"topLevel": false "topLevel": false
} });
);
}
} }
} }
} }

View file

@ -46,6 +46,10 @@ Use of multi-line label: [multi-line-label][]
Standard link: [text](https://example.com/standard) Standard link: [text](https://example.com/standard)
Wrapped in brackets: [[text][unique0]] [[unique1][]] [[unique2]]
[Embedded [text][unique3] in [unique4][] brackets [unique5]]
## Invalid Links ## Invalid Links
Missing label: [text][missing] {MD052} Missing label: [text][missing] {MD052}
@ -61,8 +65,18 @@ Space: [text] [wrong]
Empty: [text][ ] Empty: [text][ ]
Code span: `[wrong]`
Code span: `[wrong][]`
Code span: `[text][wrong]` Code span: `[text][wrong]`
Code span: `[[wrong]]`
Code span: `[[wrong][]]`
Code span: `[[text][wrong]]`
Escaped left text: \[text][wrong] Escaped left text: \[text][wrong]
Escaped right text: [text\][wrong] Escaped right text: [text\][wrong]
@ -81,6 +95,10 @@ Shortcut style: ![image]
Image in link: [![text][image]][label] [![image][]][label] [![image]][label] Image in link: [![text][image]][label] [![image][]][label] [![image]][label]
Wrapped in brackets: [![text][unique6]]
Embedded [in ![text][unique7] brackets]
## Invalid Images ## Invalid Images
Image only: ![text][missing] {MD052} Image only: ![text][missing] {MD052}
@ -119,6 +137,14 @@ Missing[^2]
[colon]: https://example.com/colon [colon]: https://example.com/colon
[multi-line-label]: [multi-line-label]:
https://example.com/multi-line-label https://example.com/multi-line-label
[unique0]: https://example.com/unique0
[unique1]: https://example.com/unique1
[unique2]: https://example.com/unique2
[unique3]: https://example.com/unique3
[unique4]: https://example.com/unique4
[unique5]: https://example.com/unique5
[unique6]: https://example.com/unique6
[unique7]: https://example.com/unique7
## Invalid Labels ## Invalid Labels

View file

@ -33688,7 +33688,7 @@ Generated by [AVA](https://avajs.dev).
15, 15,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 51, lineNumber: 55,
ruleDescription: 'Reference links and images should use a label that is defined', ruleDescription: 'Reference links and images should use a label that is defined',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052',
ruleNames: [ ruleNames: [
@ -33704,7 +33704,7 @@ Generated by [AVA](https://avajs.dev).
15, 15,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 53, lineNumber: 57,
ruleDescription: 'Reference links and images should use a label that is defined', ruleDescription: 'Reference links and images should use a label that is defined',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052',
ruleNames: [ ruleNames: [
@ -33720,7 +33720,7 @@ Generated by [AVA](https://avajs.dev).
14, 14,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 55, lineNumber: 59,
ruleDescription: 'Reference links and images should use a label that is defined', ruleDescription: 'Reference links and images should use a label that is defined',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052',
ruleNames: [ ruleNames: [
@ -33736,7 +33736,7 @@ Generated by [AVA](https://avajs.dev).
16, 16,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 86, lineNumber: 104,
ruleDescription: 'Reference links and images should use a label that is defined', ruleDescription: 'Reference links and images should use a label that is defined',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052',
ruleNames: [ ruleNames: [
@ -33752,7 +33752,7 @@ Generated by [AVA](https://avajs.dev).
16, 16,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 88, lineNumber: 106,
ruleDescription: 'Reference links and images should use a label that is defined', ruleDescription: 'Reference links and images should use a label that is defined',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052',
ruleNames: [ ruleNames: [
@ -33768,7 +33768,7 @@ Generated by [AVA](https://avajs.dev).
25, 25,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 150, lineNumber: 176,
ruleDescription: 'Reference links and images should use a label that is defined', ruleDescription: 'Reference links and images should use a label that is defined',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052',
ruleNames: [ ruleNames: [
@ -33784,7 +33784,7 @@ Generated by [AVA](https://avajs.dev).
10, 10,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 164, lineNumber: 190,
ruleDescription: 'Reference links and images should use a label that is defined', ruleDescription: 'Reference links and images should use a label that is defined',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md052',
ruleNames: [ ruleNames: [
@ -33802,7 +33802,7 @@ Generated by [AVA](https://avajs.dev).
fixInfo: { fixInfo: {
deleteCount: -1, deleteCount: -1,
}, },
lineNumber: 126, lineNumber: 152,
ruleDescription: 'Link and image reference definitions should be needed', ruleDescription: 'Link and image reference definitions should be needed',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053',
ruleNames: [ ruleNames: [
@ -33820,7 +33820,7 @@ Generated by [AVA](https://avajs.dev).
fixInfo: { fixInfo: {
deleteCount: -1, deleteCount: -1,
}, },
lineNumber: 129, lineNumber: 155,
ruleDescription: 'Link and image reference definitions should be needed', ruleDescription: 'Link and image reference definitions should be needed',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053',
ruleNames: [ ruleNames: [
@ -33838,7 +33838,7 @@ Generated by [AVA](https://avajs.dev).
fixInfo: { fixInfo: {
deleteCount: -1, deleteCount: -1,
}, },
lineNumber: 132, lineNumber: 158,
ruleDescription: 'Link and image reference definitions should be needed', ruleDescription: 'Link and image reference definitions should be needed',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053',
ruleNames: [ ruleNames: [
@ -33854,7 +33854,7 @@ Generated by [AVA](https://avajs.dev).
44, 44,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 134, lineNumber: 160,
ruleDescription: 'Link and image reference definitions should be needed', ruleDescription: 'Link and image reference definitions should be needed',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053',
ruleNames: [ ruleNames: [
@ -33870,7 +33870,7 @@ Generated by [AVA](https://avajs.dev).
44, 44,
], ],
fixInfo: null, fixInfo: null,
lineNumber: 137, lineNumber: 163,
ruleDescription: 'Link and image reference definitions should be needed', ruleDescription: 'Link and image reference definitions should be needed',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053',
ruleNames: [ ruleNames: [
@ -33927,6 +33927,10 @@ Generated by [AVA](https://avajs.dev).
Standard link: [text](https://example.com/standard)␊ Standard link: [text](https://example.com/standard)␊
Wrapped in brackets: [[text][unique0]] [[unique1][]] [[unique2]]␊
[Embedded [text][unique3] in [unique4][] brackets [unique5]]␊
## Invalid Links␊ ## Invalid Links␊
Missing label: [text][missing] {MD052}␊ Missing label: [text][missing] {MD052}␊
@ -33942,8 +33946,18 @@ Generated by [AVA](https://avajs.dev).
Empty: [text][ ]␊ Empty: [text][ ]␊
Code span: \`[wrong]\`␊
Code span: \`[wrong][]\`␊
Code span: \`[text][wrong]\`␊ Code span: \`[text][wrong]\`␊
Code span: \`[[wrong]]\`␊
Code span: \`[[wrong][]]\`␊
Code span: \`[[text][wrong]]\`␊
Escaped left text: \\[text][wrong]␊ Escaped left text: \\[text][wrong]␊
Escaped right text: [text\\][wrong]␊ Escaped right text: [text\\][wrong]␊
@ -33962,6 +33976,10 @@ Generated by [AVA](https://avajs.dev).
Image in link: [![text][image]][label] [![image][]][label] [![image]][label]␊ Image in link: [![text][image]][label] [![image][]][label] [![image]][label]␊
Wrapped in brackets: [![text][unique6]]␊
Embedded [in ![text][unique7] brackets]␊
## Invalid Images␊ ## Invalid Images␊
Image only: ![text][missing] {MD052}␊ Image only: ![text][missing] {MD052}␊
@ -34000,6 +34018,14 @@ Generated by [AVA](https://avajs.dev).
[colon]: https://example.com/colon␊ [colon]: https://example.com/colon␊
[multi-line-label]:␊ [multi-line-label]:␊
https://example.com/multi-line-label␊ https://example.com/multi-line-label␊
[unique0]: https://example.com/unique0␊
[unique1]: https://example.com/unique1␊
[unique2]: https://example.com/unique2␊
[unique3]: https://example.com/unique3␊
[unique4]: https://example.com/unique4␊
[unique5]: https://example.com/unique5␊
[unique6]: https://example.com/unique6␊
[unique7]: https://example.com/unique7␊
## Invalid Labels␊ ## Invalid Labels␊