Skip to content

Commit 48f47b5

Browse files
committed
Update MD053/link-image-reference-definitions to recognize links within square brackets (fixes #537).
1 parent 08cdd95 commit 48f47b5

File tree

5 files changed

+117
-69
lines changed

5 files changed

+117
-69
lines changed

demo/markdownlint-browser.js

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/;
5252
module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
5353
// Regular expression for all instances of emphasis markers
5454
const emphasisMarkersRe = /[_*]/g;
55-
// Regular expression for reference links (full and collapsed but not shortcut)
56-
const referenceLinkRe = /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|[^(]|$)/g;
55+
// Regular expression for reference links (full, collapsed, and shortcut)
56+
const referenceLinkRe = /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|([^(])|$)/g;
5757
// Regular expression for link reference definitions
5858
const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/;
5959
module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe;
@@ -755,7 +755,7 @@ function getReferenceLinkImageData(lineMetadata) {
755755
// Define helper functions
756756
const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
757757
const exclusions = [];
758-
const excluded = (match) => withinAnyRange(exclusions, 0, match.index, match[0].length);
758+
const excluded = (match) => withinAnyRange(exclusions, 0, match.index, match[0].length - (match[3] || "").length);
759759
// Convert input to single-line so multi-line links/images are easier
760760
const lineOffsets = [];
761761
let currentOffset = 0;
@@ -813,30 +813,29 @@ function getReferenceLinkImageData(lineMetadata) {
813813
!matchString.startsWith("!\\") &&
814814
!matchText.endsWith("\\") &&
815815
!(matchLabel || "").endsWith("\\") &&
816-
(topLevel || matchString.startsWith("!")) &&
817-
!excluded(referenceLinkMatch)) {
816+
!(topLevel && excluded(referenceLinkMatch))) {
818817
const shortcutLink = (matchLabel === undefined);
819818
const collapsedLink = (!shortcutLink && (matchLabel.length === 0));
820819
const label = normalizeLabel((shortcutLink || collapsedLink) ? matchText : matchLabel);
821820
if (label.length > 0) {
821+
const referenceindex = referenceLinkMatch.index;
822+
if (topLevel) {
823+
// Calculate line index
824+
while (lineOffsets[lineIndex + 1] <= referenceindex) {
825+
lineIndex++;
826+
}
827+
}
828+
else {
829+
// Use provided line index
830+
lineIndex = contentLineIndex;
831+
}
832+
const referenceIndex = referenceindex +
833+
(topLevel ? -lineOffsets[lineIndex] : contentIndex);
822834
if (shortcutLink) {
823-
// Track, but don't validate due to ambiguity: "text [text] text"
835+
// Track separately due to ambiguity in "text [text] text"
824836
shortcuts.add(label);
825837
}
826838
else {
827-
const referenceindex = referenceLinkMatch.index;
828-
if (topLevel) {
829-
// Calculate line index
830-
while (lineOffsets[lineIndex + 1] <= referenceindex) {
831-
lineIndex++;
832-
}
833-
}
834-
else {
835-
// Use provided line index
836-
lineIndex = contentLineIndex;
837-
}
838-
const referenceIndex = referenceindex +
839-
(topLevel ? -lineOffsets[lineIndex] : contentIndex);
840839
// Track reference and location
841840
const referenceData = references.get(label) || [];
842841
referenceData.push([
@@ -845,15 +844,15 @@ function getReferenceLinkImageData(lineMetadata) {
845844
matchString.length
846845
]);
847846
references.set(label, referenceData);
848-
// Check for images embedded in top-level link text
849-
if (!matchString.startsWith("!")) {
850-
pendingContents.push({
851-
"content": matchText,
852-
"contentLineIndex": lineIndex,
853-
"contentIndex": referenceIndex + 1,
854-
"topLevel": false
855-
});
856-
}
847+
}
848+
// Check for links embedded in brackets
849+
if (!matchString.startsWith("!")) {
850+
pendingContents.push({
851+
"content": matchText,
852+
"contentLineIndex": lineIndex,
853+
"contentIndex": referenceIndex + 1,
854+
"topLevel": false
855+
});
857856
}
858857
}
859858
}

helpers/helpers.js

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
3030
// Regular expression for all instances of emphasis markers
3131
const emphasisMarkersRe = /[_*]/g;
3232

33-
// Regular expression for reference links (full and collapsed but not shortcut)
33+
// Regular expression for reference links (full, collapsed, and shortcut)
3434
const referenceLinkRe =
35-
/!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|[^(]|$)/g;
35+
/!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|([^(])|$)/g;
3636

3737
// Regular expression for link reference definitions
3838
const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/;
@@ -785,7 +785,7 @@ function getReferenceLinkImageData(lineMetadata) {
785785
const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
786786
const exclusions = [];
787787
const excluded = (match) => withinAnyRange(
788-
exclusions, 0, match.index, match[0].length
788+
exclusions, 0, match.index, match[0].length - (match[3] || "").length
789789
);
790790
// Convert input to single-line so multi-line links/images are easier
791791
const lineOffsets = [];
@@ -845,8 +845,7 @@ function getReferenceLinkImageData(lineMetadata) {
845845
!matchString.startsWith("!\\") &&
846846
!matchText.endsWith("\\") &&
847847
!(matchLabel || "").endsWith("\\") &&
848-
(topLevel || matchString.startsWith("!")) &&
849-
!excluded(referenceLinkMatch)
848+
!(topLevel && excluded(referenceLinkMatch))
850849
) {
851850
const shortcutLink = (matchLabel === undefined);
852851
const collapsedLink =
@@ -855,22 +854,22 @@ function getReferenceLinkImageData(lineMetadata) {
855854
(shortcutLink || collapsedLink) ? matchText : matchLabel
856855
);
857856
if (label.length > 0) {
857+
const referenceindex = referenceLinkMatch.index;
858+
if (topLevel) {
859+
// Calculate line index
860+
while (lineOffsets[lineIndex + 1] <= referenceindex) {
861+
lineIndex++;
862+
}
863+
} else {
864+
// Use provided line index
865+
lineIndex = contentLineIndex;
866+
}
867+
const referenceIndex = referenceindex +
868+
(topLevel ? -lineOffsets[lineIndex] : contentIndex);
858869
if (shortcutLink) {
859-
// Track, but don't validate due to ambiguity: "text [text] text"
870+
// Track separately due to ambiguity in "text [text] text"
860871
shortcuts.add(label);
861872
} 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);
874873
// Track reference and location
875874
const referenceData = references.get(label) || [];
876875
referenceData.push([
@@ -879,17 +878,15 @@ function getReferenceLinkImageData(lineMetadata) {
879878
matchString.length
880879
]);
881880
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-
}
881+
}
882+
// Check for links embedded in brackets
883+
if (!matchString.startsWith("!")) {
884+
pendingContents.push({
885+
"content": matchText,
886+
"contentLineIndex": lineIndex,
887+
"contentIndex": referenceIndex + 1,
888+
"topLevel": false
889+
});
893890
}
894891
}
895892
}

test/reference-links-and-images.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Use of multi-line label: [multi-line-label][]
4646

4747
Standard link: [text](https://example.com/standard)
4848

49+
Wrapped in brackets: [[text][unique0]] [[unique1][]] [[unique2]]
50+
51+
[Embedded [text][unique3] in [unique4][] brackets [unique5]]
52+
4953
## Invalid Links
5054

5155
Missing label: [text][missing] {MD052}
@@ -61,8 +65,18 @@ Space: [text] [wrong]
6165

6266
Empty: [text][ ]
6367

68+
Code span: `[wrong]`
69+
70+
Code span: `[wrong][]`
71+
6472
Code span: `[text][wrong]`
6573

74+
Code span: `[[wrong]]`
75+
76+
Code span: `[[wrong][]]`
77+
78+
Code span: `[[text][wrong]]`
79+
6680
Escaped left text: \[text][wrong]
6781

6882
Escaped right text: [text\][wrong]
@@ -81,6 +95,10 @@ Shortcut style: ![image]
8195

8296
Image in link: [![text][image]][label] [![image][]][label] [![image]][label]
8397

98+
Wrapped in brackets: [![text][unique6]]
99+
100+
Embedded [in ![text][unique7] brackets]
101+
84102
## Invalid Images
85103

86104
Image only: ![text][missing] {MD052}
@@ -119,6 +137,14 @@ Missing[^2]
119137
[colon]: https://example.com/colon
120138
[multi-line-label]:
121139
https://example.com/multi-line-label
140+
[unique0]: https://example.com/unique0
141+
[unique1]: https://example.com/unique1
142+
[unique2]: https://example.com/unique2
143+
[unique3]: https://example.com/unique3
144+
[unique4]: https://example.com/unique4
145+
[unique5]: https://example.com/unique5
146+
[unique6]: https://example.com/unique6
147+
[unique7]: https://example.com/unique7
122148

123149
## Invalid Labels
124150

0 commit comments

Comments
 (0)