diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 074a137d3..462d282d8 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -52,8 +52,8 @@ module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/; module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/; // Regular expression for all instances of emphasis markers const emphasisMarkersRe = /[_*]/g; -// Regular expression for reference links (full and collapsed but not shortcut) -const referenceLinkRe = /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|[^(]|$)/g; +// Regular expression for reference links (full, collapsed, and shortcut) +const referenceLinkRe = /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|([^(])|$)/g; // Regular expression for link reference definitions const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/; module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe; @@ -755,7 +755,7 @@ function getReferenceLinkImageData(lineMetadata) { // Define helper functions const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); 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 const lineOffsets = []; let currentOffset = 0; @@ -813,30 +813,29 @@ function getReferenceLinkImageData(lineMetadata) { !matchString.startsWith("!\\") && !matchText.endsWith("\\") && !(matchLabel || "").endsWith("\\") && - (topLevel || matchString.startsWith("!")) && - !excluded(referenceLinkMatch)) { + !(topLevel && excluded(referenceLinkMatch))) { const shortcutLink = (matchLabel === undefined); const collapsedLink = (!shortcutLink && (matchLabel.length === 0)); const label = normalizeLabel((shortcutLink || collapsedLink) ? matchText : matchLabel); 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) { - // Track, but don't validate due to ambiguity: "text [text] text" + // Track separately due to ambiguity in "text [text] text" shortcuts.add(label); } 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 const referenceData = references.get(label) || []; referenceData.push([ @@ -845,15 +844,15 @@ function getReferenceLinkImageData(lineMetadata) { matchString.length ]); references.set(label, referenceData); - // Check for images embedded in top-level link text - if (!matchString.startsWith("!")) { - pendingContents.push({ - "content": matchText, - "contentLineIndex": lineIndex, - "contentIndex": referenceIndex + 1, - "topLevel": false - }); - } + } + // Check for links embedded in brackets + if (!matchString.startsWith("!")) { + pendingContents.push({ + "content": matchText, + "contentLineIndex": lineIndex, + "contentIndex": referenceIndex + 1, + "topLevel": false + }); } } } diff --git a/helpers/helpers.js b/helpers/helpers.js index c1b1ccc54..3ce55662b 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -30,9 +30,9 @@ module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/; // Regular expression for all instances of emphasis markers 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; + /!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|([^(])|$)/g; // Regular expression for link reference definitions const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/; @@ -785,7 +785,7 @@ function getReferenceLinkImageData(lineMetadata) { const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); const exclusions = []; 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 const lineOffsets = []; @@ -845,8 +845,7 @@ function getReferenceLinkImageData(lineMetadata) { !matchString.startsWith("!\\") && !matchText.endsWith("\\") && !(matchLabel || "").endsWith("\\") && - (topLevel || matchString.startsWith("!")) && - !excluded(referenceLinkMatch) + !(topLevel && excluded(referenceLinkMatch)) ) { const shortcutLink = (matchLabel === undefined); const collapsedLink = @@ -855,22 +854,22 @@ function getReferenceLinkImageData(lineMetadata) { (shortcutLink || collapsedLink) ? matchText : matchLabel ); 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) { - // Track, but don't validate due to ambiguity: "text [text] text" + // Track separately due to ambiguity in "text [text] text" shortcuts.add(label); } 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 const referenceData = references.get(label) || []; referenceData.push([ @@ -879,17 +878,15 @@ function getReferenceLinkImageData(lineMetadata) { matchString.length ]); references.set(label, referenceData); - // Check for images embedded in top-level link text - if (!matchString.startsWith("!")) { - pendingContents.push( - { - "content": matchText, - "contentLineIndex": lineIndex, - "contentIndex": referenceIndex + 1, - "topLevel": false - } - ); - } + } + // Check for links embedded in brackets + if (!matchString.startsWith("!")) { + pendingContents.push({ + "content": matchText, + "contentLineIndex": lineIndex, + "contentIndex": referenceIndex + 1, + "topLevel": false + }); } } } diff --git a/test/reference-links-and-images.md b/test/reference-links-and-images.md index 97f41b5d1..80d53e703 100644 --- a/test/reference-links-and-images.md +++ b/test/reference-links-and-images.md @@ -46,6 +46,10 @@ Use of multi-line label: [multi-line-label][] Standard link: [text](https://example.com/standard) +Wrapped in brackets: [[text][unique0]] [[unique1][]] [[unique2]] + +[Embedded [text][unique3] in [unique4][] brackets [unique5]] + ## Invalid Links Missing label: [text][missing] {MD052} @@ -61,8 +65,18 @@ Space: [text] [wrong] Empty: [text][ ] +Code span: `[wrong]` + +Code span: `[wrong][]` + Code span: `[text][wrong]` +Code span: `[[wrong]]` + +Code span: `[[wrong][]]` + +Code span: `[[text][wrong]]` + Escaped left 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] +Wrapped in brackets: [![text][unique6]] + +Embedded [in ![text][unique7] brackets] + ## Invalid Images Image only: ![text][missing] {MD052} @@ -119,6 +137,14 @@ Missing[^2] [colon]: https://example.com/colon [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 diff --git a/test/snapshots/markdownlint-test-scenarios.js.md b/test/snapshots/markdownlint-test-scenarios.js.md index aed2c4e40..3e954792b 100644 --- a/test/snapshots/markdownlint-test-scenarios.js.md +++ b/test/snapshots/markdownlint-test-scenarios.js.md @@ -33688,7 +33688,7 @@ Generated by [AVA](https://avajs.dev). 15, ], fixInfo: null, - lineNumber: 51, + lineNumber: 55, 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', ruleNames: [ @@ -33704,7 +33704,7 @@ Generated by [AVA](https://avajs.dev). 15, ], fixInfo: null, - lineNumber: 53, + lineNumber: 57, 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', ruleNames: [ @@ -33720,7 +33720,7 @@ Generated by [AVA](https://avajs.dev). 14, ], fixInfo: null, - lineNumber: 55, + lineNumber: 59, 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', ruleNames: [ @@ -33736,7 +33736,7 @@ Generated by [AVA](https://avajs.dev). 16, ], fixInfo: null, - lineNumber: 86, + lineNumber: 104, 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', ruleNames: [ @@ -33752,7 +33752,7 @@ Generated by [AVA](https://avajs.dev). 16, ], fixInfo: null, - lineNumber: 88, + lineNumber: 106, 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', ruleNames: [ @@ -33768,7 +33768,7 @@ Generated by [AVA](https://avajs.dev). 25, ], fixInfo: null, - lineNumber: 150, + lineNumber: 176, 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', ruleNames: [ @@ -33784,7 +33784,7 @@ Generated by [AVA](https://avajs.dev). 10, ], fixInfo: null, - lineNumber: 164, + lineNumber: 190, 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', ruleNames: [ @@ -33802,7 +33802,7 @@ Generated by [AVA](https://avajs.dev). fixInfo: { deleteCount: -1, }, - lineNumber: 126, + lineNumber: 152, ruleDescription: 'Link and image reference definitions should be needed', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleNames: [ @@ -33820,7 +33820,7 @@ Generated by [AVA](https://avajs.dev). fixInfo: { deleteCount: -1, }, - lineNumber: 129, + lineNumber: 155, ruleDescription: 'Link and image reference definitions should be needed', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleNames: [ @@ -33838,7 +33838,7 @@ Generated by [AVA](https://avajs.dev). fixInfo: { deleteCount: -1, }, - lineNumber: 132, + lineNumber: 158, ruleDescription: 'Link and image reference definitions should be needed', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleNames: [ @@ -33854,7 +33854,7 @@ Generated by [AVA](https://avajs.dev). 44, ], fixInfo: null, - lineNumber: 134, + lineNumber: 160, ruleDescription: 'Link and image reference definitions should be needed', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleNames: [ @@ -33870,7 +33870,7 @@ Generated by [AVA](https://avajs.dev). 44, ], fixInfo: null, - lineNumber: 137, + lineNumber: 163, ruleDescription: 'Link and image reference definitions should be needed', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md053', ruleNames: [ @@ -33927,6 +33927,10 @@ Generated by [AVA](https://avajs.dev). ␊ Standard link: [text](https://example.com/standard)␊ ␊ + Wrapped in brackets: [[text][unique0]] [[unique1][]] [[unique2]]␊ + ␊ + [Embedded [text][unique3] in [unique4][] brackets [unique5]]␊ + ␊ ## Invalid Links␊ ␊ Missing label: [text][missing] {MD052}␊ @@ -33942,8 +33946,18 @@ Generated by [AVA](https://avajs.dev). ␊ Empty: [text][ ]␊ ␊ + Code span: \`[wrong]\`␊ + ␊ + Code span: \`[wrong][]\`␊ + ␊ Code span: \`[text][wrong]\`␊ ␊ + Code span: \`[[wrong]]\`␊ + ␊ + Code span: \`[[wrong][]]\`␊ + ␊ + Code span: \`[[text][wrong]]\`␊ + ␊ Escaped left 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]␊ ␊ + Wrapped in brackets: [![text][unique6]]␊ + ␊ + Embedded [in ![text][unique7] brackets]␊ + ␊ ## Invalid Images␊ ␊ Image only: ![text][missing] {MD052}␊ @@ -34000,6 +34018,14 @@ Generated by [AVA](https://avajs.dev). [colon]: https://example.com/colon␊ [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␊ ␊ diff --git a/test/snapshots/markdownlint-test-scenarios.js.snap b/test/snapshots/markdownlint-test-scenarios.js.snap index 1b203b7e5..5769f0d83 100644 Binary files a/test/snapshots/markdownlint-test-scenarios.js.snap and b/test/snapshots/markdownlint-test-scenarios.js.snap differ