diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 1f4887b2..1cbd5912 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -5656,10 +5656,9 @@ module.exports = { -const { addErrorContext, filterTokens } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); - -const spaceInLinkRe = - /\[(?:\s[^\]]*|[^\]]*?\s)\](?=(\([^)]*\)|\[[^\]]*\]))/; +const { addErrorContext } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); +const { filterByTypes } = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"); +const { referenceLinkImageData } = __webpack_require__(/*! ./cache */ "../lib/cache.js"); // eslint-disable-next-line jsdoc/valid-types /** @type import("./markdownlint").Rule */ @@ -5667,60 +5666,55 @@ module.exports = { "names": [ "MD039", "no-space-in-links" ], "description": "Spaces inside link text", "tags": [ "whitespace", "links" ], - "parser": "markdownit", + "parser": "micromark", "function": function MD039(params, onError) { - filterTokens(params, "inline", (token) => { - const { children } = token; - let { lineNumber } = token; - let inLink = false; - let linkText = ""; - let lineIndex = 0; - for (const child of children) { - const { content, markup, type } = child; - if (type === "link_open") { - inLink = true; - linkText = ""; - } else if (type === "link_close") { - inLink = false; - const left = linkText.trimStart().length !== linkText.length; - const right = linkText.trimEnd().length !== linkText.length; - if (left || right) { - const line = params.lines[lineNumber - 1]; - let range = null; - let fixInfo = null; - const match = line.slice(lineIndex).match(spaceInLinkRe); - if (match) { - // @ts-ignore - const column = match.index + lineIndex + 1; - const length = match[0].length; - range = [ column, length ]; - fixInfo = { - "editColumn": column + 1, - "deleteCount": length - 2, - "insertText": linkText.trim() - }; - lineIndex = column + length - 1; - } - addErrorContext( - onError, - lineNumber, - `[${linkText}]`, - left, - right, - range, - fixInfo - ); + const { definitions } = referenceLinkImageData(); + const labels = filterByTypes( + params.parsers.micromark.tokens, + [ "label" ] + ).filter((label) => label.parent?.type === "link"); + for (const label of labels) { + const labelTexts = filterByTypes(label.children, [ "labelText" ]); + for (const labelText of labelTexts) { + const leftSpace = + labelText.text.trimStart().length !== labelText.text.length; + const rightSpace = + labelText.text.trimEnd().length !== labelText.text.length; + if ( + (leftSpace || rightSpace) && + // Ignore non-shortcut link content "[ text ]" + ((label.parent?.children.length !== 1) || definitions.has(labelText.text.trim())) + ) { + // eslint-disable-next-line no-undef-init + let range = undefined; + if (label.startLine === label.endLine) { + const labelColumn = label.startColumn; + const labelLength = label.endColumn - label.startColumn; + range = [ labelColumn, labelLength ]; } - } else if ((type === "softbreak") || (type === "hardbreak")) { - lineNumber++; - lineIndex = 0; - } else if (inLink) { - linkText += type.endsWith("_inline") ? - `${markup}${content}${markup}` : - (content || markup); + // eslint-disable-next-line no-undef-init + let fixInfo = undefined; + if (labelText.startLine === labelText.endLine) { + const textColumn = labelText.startColumn; + const textLength = labelText.endColumn - labelText.startColumn; + fixInfo = { + "editColumn": textColumn, + "deleteCount": textLength, + "insertText": labelText.text.trim() + }; + } + addErrorContext( + onError, + labelText.startLine, + label.text.replace(/\s+/g, " "), + leftSpace, + rightSpace, + range, + fixInfo + ); } } - }); + } } }; diff --git a/lib/md039.js b/lib/md039.js index 676c603c..31757b07 100644 --- a/lib/md039.js +++ b/lib/md039.js @@ -2,10 +2,9 @@ "use strict"; -const { addErrorContext, filterTokens } = require("../helpers"); - -const spaceInLinkRe = - /\[(?:\s[^\]]*|[^\]]*?\s)\](?=(\([^)]*\)|\[[^\]]*\]))/; +const { addErrorContext } = require("../helpers"); +const { filterByTypes } = require("../helpers/micromark.cjs"); +const { referenceLinkImageData } = require("./cache"); // eslint-disable-next-line jsdoc/valid-types /** @type import("./markdownlint").Rule */ @@ -13,59 +12,54 @@ module.exports = { "names": [ "MD039", "no-space-in-links" ], "description": "Spaces inside link text", "tags": [ "whitespace", "links" ], - "parser": "markdownit", + "parser": "micromark", "function": function MD039(params, onError) { - filterTokens(params, "inline", (token) => { - const { children } = token; - let { lineNumber } = token; - let inLink = false; - let linkText = ""; - let lineIndex = 0; - for (const child of children) { - const { content, markup, type } = child; - if (type === "link_open") { - inLink = true; - linkText = ""; - } else if (type === "link_close") { - inLink = false; - const left = linkText.trimStart().length !== linkText.length; - const right = linkText.trimEnd().length !== linkText.length; - if (left || right) { - const line = params.lines[lineNumber - 1]; - let range = null; - let fixInfo = null; - const match = line.slice(lineIndex).match(spaceInLinkRe); - if (match) { - // @ts-ignore - const column = match.index + lineIndex + 1; - const length = match[0].length; - range = [ column, length ]; - fixInfo = { - "editColumn": column + 1, - "deleteCount": length - 2, - "insertText": linkText.trim() - }; - lineIndex = column + length - 1; - } - addErrorContext( - onError, - lineNumber, - `[${linkText}]`, - left, - right, - range, - fixInfo - ); + const { definitions } = referenceLinkImageData(); + const labels = filterByTypes( + params.parsers.micromark.tokens, + [ "label" ] + ).filter((label) => label.parent?.type === "link"); + for (const label of labels) { + const labelTexts = filterByTypes(label.children, [ "labelText" ]); + for (const labelText of labelTexts) { + const leftSpace = + labelText.text.trimStart().length !== labelText.text.length; + const rightSpace = + labelText.text.trimEnd().length !== labelText.text.length; + if ( + (leftSpace || rightSpace) && + // Ignore non-shortcut link content "[ text ]" + ((label.parent?.children.length !== 1) || definitions.has(labelText.text.trim())) + ) { + // eslint-disable-next-line no-undef-init + let range = undefined; + if (label.startLine === label.endLine) { + const labelColumn = label.startColumn; + const labelLength = label.endColumn - label.startColumn; + range = [ labelColumn, labelLength ]; } - } else if ((type === "softbreak") || (type === "hardbreak")) { - lineNumber++; - lineIndex = 0; - } else if (inLink) { - linkText += type.endsWith("_inline") ? - `${markup}${content}${markup}` : - (content || markup); + // eslint-disable-next-line no-undef-init + let fixInfo = undefined; + if (labelText.startLine === labelText.endLine) { + const textColumn = labelText.startColumn; + const textLength = labelText.endColumn - labelText.startColumn; + fixInfo = { + "editColumn": textColumn, + "deleteCount": textLength, + "insertText": labelText.text.trim() + }; + } + addErrorContext( + onError, + labelText.startLine, + label.text.replace(/\s+/g, " "), + leftSpace, + rightSpace, + range, + fixInfo + ); } } - }); + } } }; diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index ab158bcc..16ff3fca 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -1104,20 +1104,13 @@ test("someCustomRulesHaveValidUrl", (t) => { }); test("markdownItPluginsSingle", (t) => new Promise((resolve) => { - t.plan(2); + t.plan(4); markdownlint({ "strings": { - "string": "# Heading\n\nText [ link ](https://example.com)\n" + "string": "# Heading\n\nText\n" }, "markdownItPlugins": [ - [ - pluginInline, - "trim_text_plugin", - "text", - function iterator(tokens, index) { - tokens[index].content = tokens[index].content.trim(); - } - ] + [ pluginInline, "check_text_plugin", "text", () => t.true(true) ] ] }, function callback(err, actual) { t.falsy(err); diff --git a/test/snapshots/markdownlint-test-scenarios.js.md b/test/snapshots/markdownlint-test-scenarios.js.md index 530873af..80b44a1e 100644 --- a/test/snapshots/markdownlint-test-scenarios.js.md +++ b/test/snapshots/markdownlint-test-scenarios.js.md @@ -51692,11 +51692,11 @@ Generated by [AVA](https://avajs.dev). ], }, { - errorContext: '[ link with leading space]', + errorContext: '[ link with lea...space {MD039} ]', errorDetail: null, errorRange: null, fixInfo: null, - lineNumber: 52, + lineNumber: 51, ruleDescription: 'Spaces inside link text', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md', ruleNames: [ @@ -51784,6 +51784,126 @@ Generated by [AVA](https://avajs.dev). 'no-space-in-links', ], }, + { + errorContext: '[ref ]', + errorDetail: null, + errorRange: [ + 1, + 6, + ], + fixInfo: { + deleteCount: 4, + editColumn: 2, + insertText: 'ref', + }, + lineNumber: 68, + ruleDescription: 'Spaces inside link text', + ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md', + ruleNames: [ + 'MD039', + 'no-space-in-links', + ], + }, + { + errorContext: '[ ref]', + errorDetail: null, + errorRange: [ + 1, + 6, + ], + fixInfo: { + deleteCount: 4, + editColumn: 2, + insertText: 'ref', + }, + lineNumber: 70, + ruleDescription: 'Spaces inside link text', + ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md', + ruleNames: [ + 'MD039', + 'no-space-in-links', + ], + }, + { + errorContext: '[ ref ]', + errorDetail: null, + errorRange: [ + 1, + 7, + ], + fixInfo: { + deleteCount: 5, + editColumn: 2, + insertText: 'ref', + }, + lineNumber: 72, + ruleDescription: 'Spaces inside link text', + ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md', + ruleNames: [ + 'MD039', + 'no-space-in-links', + ], + }, + { + errorContext: '[ref ]', + errorDetail: null, + errorRange: [ + 1, + 6, + ], + fixInfo: { + deleteCount: 4, + editColumn: 2, + insertText: 'ref', + }, + lineNumber: 76, + ruleDescription: 'Spaces inside link text', + ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md', + ruleNames: [ + 'MD039', + 'no-space-in-links', + ], + }, + { + errorContext: '[ ref]', + errorDetail: null, + errorRange: [ + 1, + 6, + ], + fixInfo: { + deleteCount: 4, + editColumn: 2, + insertText: 'ref', + }, + lineNumber: 78, + ruleDescription: 'Spaces inside link text', + ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md', + ruleNames: [ + 'MD039', + 'no-space-in-links', + ], + }, + { + errorContext: '[ ref ]', + errorDetail: null, + errorRange: [ + 1, + 7, + ], + fixInfo: { + deleteCount: 5, + editColumn: 2, + insertText: 'ref', + }, + lineNumber: 80, + ruleDescription: 'Spaces inside link text', + ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md', + ruleNames: [ + 'MD039', + 'no-space-in-links', + ], + }, ], fixed: `# Spaces Inside Link Text␊ ␊ @@ -51835,8 +51955,8 @@ Generated by [AVA](https://avajs.dev). [with](spaces) ␊ [error]({MD039})␊ ␊ - Wrapped [ link with leading space␊ - ](https://example.com) {MD039}␊ + Wrapped [ link with leading space {MD039}␊ + ](https://example.com)␊ ␊ Non-wrapped [link with leading space](https://example.com) {MD039}␊ ␊ @@ -51850,7 +51970,27 @@ Generated by [AVA](https://avajs.dev). ␊ [link][ref] {MD039}␊ ␊ + [ref]␊ + ␊ + [ref] {MD039}␊ + ␊ + [ref] {MD039}␊ + ␊ + [ref] {MD039}␊ + ␊ + [ref][]␊ + ␊ + [ref][] {MD039}␊ + ␊ + [ref][] {MD039}␊ + ␊ + [ref][] {MD039}␊ + ␊ [ref]: https://example.com␊ + ␊ + Not a link, just [ text in ] brackets␊ + ␊ + Images are ![ not links ](image.jpg)␊ `, } diff --git a/test/snapshots/markdownlint-test-scenarios.js.snap b/test/snapshots/markdownlint-test-scenarios.js.snap index 8bbe46a8..0ec57a42 100644 Binary files a/test/snapshots/markdownlint-test-scenarios.js.snap and b/test/snapshots/markdownlint-test-scenarios.js.snap differ diff --git a/test/spaces_inside_link_text.md b/test/spaces_inside_link_text.md index df11518b..8c0a1881 100644 --- a/test/spaces_inside_link_text.md +++ b/test/spaces_inside_link_text.md @@ -48,8 +48,8 @@ function MoreCodeButNotCode(input) { [with](spaces) [error ]({MD039}) -Wrapped [ link with leading space - ](https://example.com) {MD039} +Wrapped [ link with leading space {MD039} + ](https://example.com) Non-wrapped [ link with leading space](https://example.com) {MD039} @@ -63,4 +63,24 @@ Non-wrapped [ link with leading space](https://example.com) {MD039} [ link ][ref] {MD039} +[ref] + +[ref ] {MD039} + +[ ref] {MD039} + +[ ref ] {MD039} + +[ref][] + +[ref ][] {MD039} + +[ ref][] {MD039} + +[ ref ][] {MD039} + [ref]: https://example.com + +Not a link, just [ text in ] brackets + +Images are ![ not links ](image.jpg)