diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index cd476311..78ffebdc 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -1374,10 +1374,12 @@ var _require = __webpack_require__(/*! markdownlint-micromark */ "markdownlint-m * * @param {string} markdown Markdown document. * @param {Object} [options] Options for micromark. + * @param {boolean} [refsDefined] Whether to treat references as defined. * @returns {Object[]} Micromark events. */ function getMicromarkEvents(markdown) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var refsDefined = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; // Customize options object to add useful extensions options.extensions = options.extensions || []; options.extensions.push(gfmAutolinkLiteral, gfmFootnote(), gfmTable); @@ -1386,10 +1388,12 @@ function getMicromarkEvents(markdown) { var encoding = undefined; var eol = true; var parseContext = parse(options); - // Customize ParseContext to treat all references as defined - parseContext.defined.includes = function (searchElement) { - return searchElement.length > 0; - }; + if (refsDefined) { + // Customize ParseContext to treat all references as defined + parseContext.defined.includes = function (searchElement) { + return searchElement.length > 0; + }; + } var chunks = preprocess()(markdown, encoding, eol); var events = postprocess(parseContext.document().write(chunks)); return events; @@ -1400,12 +1404,14 @@ function getMicromarkEvents(markdown) { * * @param {string} markdown Markdown document. * @param {Object} [options] Options for micromark. + * @param {boolean} [refsDefined] Whether to treat references as defined. * @returns {Token[]} Micromark tokens (frozen). */ function micromarkParse(markdown) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var refsDefined = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; // Use micromark to parse document into Events - var events = getMicromarkEvents(markdown, options); + var events = getMicromarkEvents(markdown, options, refsDefined); // Create Token objects var document = []; @@ -5008,60 +5014,68 @@ var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorContext = _require.addErrorContext; var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"), filterByPredicate = _require2.filterByPredicate, - getHtmlTagInfo = _require2.getHtmlTagInfo; + getHtmlTagInfo = _require2.getHtmlTagInfo, + parse = _require2.parse; module.exports = { "names": ["MD034", "no-bare-urls"], "description": "Bare URL used", "tags": ["links", "url"], "function": function MD034(params, onError) { - var literalAutolinks = filterByPredicate(params.parsers.micromark.tokens, function (token) { - return token.type === "literalAutolink"; - }, function (token) { - var children = token.children; - var result = []; - for (var i = 0; i < children.length; i++) { - var openToken = children[i]; - var openTagInfo = getHtmlTagInfo(openToken); - if (openTagInfo && !openTagInfo.close) { - var count = 1; - for (var j = i + 1; j < children.length; j++) { - var closeToken = children[j]; - var closeTagInfo = getHtmlTagInfo(closeToken); - if (closeTagInfo && openTagInfo.name === closeTagInfo.name) { - if (closeTagInfo.close) { - count--; - if (count === 0) { - i = j; - break; + var literalAutolinks = function literalAutolinks(tokens) { + return filterByPredicate(tokens, function (token) { + return token.type === "literalAutolink"; + }, function (token) { + var children = token.children; + var result = []; + for (var i = 0; i < children.length; i++) { + var openToken = children[i]; + var openTagInfo = getHtmlTagInfo(openToken); + if (openTagInfo && !openTagInfo.close) { + var count = 1; + for (var j = i + 1; j < children.length; j++) { + var closeToken = children[j]; + var closeTagInfo = getHtmlTagInfo(closeToken); + if (closeTagInfo && openTagInfo.name === closeTagInfo.name) { + if (closeTagInfo.close) { + count--; + if (count === 0) { + i = j; + break; + } + } else { + count++; } - } else { - count++; } } + } else { + result.push(openToken); } - } else { - result.push(openToken); } + return result; + }); + }; + if (literalAutolinks(params.parsers.micromark.tokens).length > 0) { + // Re-parse with correct link/image reference definition handling + var document = params.lines.join("\n"); + var tokens = parse(document, undefined, false); + var _iterator = _createForOfIteratorHelper(literalAutolinks(tokens)), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var token = _step.value; + var range = [token.startColumn, token.endColumn - token.startColumn]; + var fixInfo = { + "editColumn": range[0], + "deleteCount": range[1], + "insertText": "<".concat(token.text, ">") + }; + addErrorContext(onError, token.startLine, token.text, null, null, range, fixInfo); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); } - return result; - }); - var _iterator = _createForOfIteratorHelper(literalAutolinks), - _step; - try { - for (_iterator.s(); !(_step = _iterator.n()).done;) { - var token = _step.value; - var range = [token.startColumn, token.endColumn - token.startColumn]; - var fixInfo = { - "editColumn": range[0], - "deleteCount": range[1], - "insertText": "<".concat(token.text, ">") - }; - addErrorContext(onError, token.startLine, token.text, null, null, range, fixInfo); - } - } catch (err) { - _iterator.e(err); - } finally { - _iterator.f(); } } }; diff --git a/helpers/micromark.cjs b/helpers/micromark.cjs index a36c6e3e..6dd587e3 100644 --- a/helpers/micromark.cjs +++ b/helpers/micromark.cjs @@ -26,9 +26,10 @@ const { * * @param {string} markdown Markdown document. * @param {Object} [options] Options for micromark. + * @param {boolean} [refsDefined] Whether to treat references as defined. * @returns {Object[]} Micromark events. */ -function getMicromarkEvents(markdown, options = {}) { +function getMicromarkEvents(markdown, options = {}, refsDefined = true) { // Customize options object to add useful extensions options.extensions = options.extensions || []; @@ -38,8 +39,10 @@ function getMicromarkEvents(markdown, options = {}) { const encoding = undefined; const eol = true; const parseContext = parse(options); - // Customize ParseContext to treat all references as defined - parseContext.defined.includes = (searchElement) => searchElement.length > 0; + if (refsDefined) { + // Customize ParseContext to treat all references as defined + parseContext.defined.includes = (searchElement) => searchElement.length > 0; + } const chunks = preprocess()(markdown, encoding, eol); const events = postprocess(parseContext.document().write(chunks)); return events; @@ -50,12 +53,14 @@ function getMicromarkEvents(markdown, options = {}) { * * @param {string} markdown Markdown document. * @param {Object} [options] Options for micromark. + * @param {boolean} [refsDefined] Whether to treat references as defined. * @returns {Token[]} Micromark tokens (frozen). */ -function micromarkParse(markdown, options = {}) { +function micromarkParse(markdown, options = {}, refsDefined = true) { // Use micromark to parse document into Events - const events = getMicromarkEvents(markdown, options); + const events = + getMicromarkEvents(markdown, options, refsDefined); // Create Token objects const document = []; diff --git a/lib/md034.js b/lib/md034.js index 96f7040a..4051c36f 100644 --- a/lib/md034.js +++ b/lib/md034.js @@ -3,7 +3,7 @@ "use strict"; const { addErrorContext } = require("../helpers"); -const { filterByPredicate, getHtmlTagInfo } = +const { filterByPredicate, getHtmlTagInfo, parse } = require("../helpers/micromark.cjs"); module.exports = { @@ -11,9 +11,9 @@ module.exports = { "description": "Bare URL used", "tags": [ "links", "url" ], "function": function MD034(params, onError) { - const literalAutolinks = + const literalAutolinks = (tokens) => ( filterByPredicate( - params.parsers.micromark.tokens, + tokens, (token) => token.type === "literalAutolink", (token) => { const { children } = token; @@ -43,26 +43,33 @@ module.exports = { } } return result; - }); - for (const token of literalAutolinks) { - const range = [ - token.startColumn, - token.endColumn - token.startColumn - ]; - const fixInfo = { - "editColumn": range[0], - "deleteCount": range[1], - "insertText": `<${token.text}>` - }; - addErrorContext( - onError, - token.startLine, - token.text, - null, - null, - range, - fixInfo - ); + } + ) + ); + if (literalAutolinks(params.parsers.micromark.tokens).length > 0) { + // Re-parse with correct link/image reference definition handling + const document = params.lines.join("\n"); + const tokens = parse(document, undefined, false); + for (const token of literalAutolinks(tokens)) { + const range = [ + token.startColumn, + token.endColumn - token.startColumn + ]; + const fixInfo = { + "editColumn": range[0], + "deleteCount": range[1], + "insertText": `<${token.text}>` + }; + addErrorContext( + onError, + token.startLine, + token.text, + null, + null, + range, + fixInfo + ); + } } } }; diff --git a/test/bare-urls.md b/test/bare-urls.md index 2455fe39..67b83cca 100644 --- a/test/bare-urls.md +++ b/test/bare-urls.md @@ -83,8 +83,7 @@ Angle brackets work the same for email: Links bind to the innermost [link that [is-a-valid] link](https://example.com) {MD034} -But not if the [link [is-not-a-valid] link](https://example.com) {MD034} -HOWEVER this scenario could have an invalid shortcut and IS reported +But not if the [link [is-not-a-valid] link](https://example.com) -Escaping both inner square brackets avoids the unwanted report: +Escaping both inner square brackets avoids confusion: [link \[is-not-a-valid\] link](https://example.com) diff --git a/test/links.md b/test/links.md index 2eabaa93..6a4014f2 100644 --- a/test/links.md +++ b/test/links.md @@ -25,3 +25,8 @@ Duplicate links in tables should be handled: | Link | Same Link | Violation | |----------------------|----------------------|-----------| | https://example.com/ | https://example.com/ | {MD034} | + +This is not a bare URL: [text [undefined] text](https://example.com). +This is a bare URL: [text [defined] text](https://example.com). {MD034} + +[defined]: https://example.com diff --git a/test/snapshots/markdownlint-test-scenarios.js.md b/test/snapshots/markdownlint-test-scenarios.js.md index a799ea6e..5f61ece5 100644 --- a/test/snapshots/markdownlint-test-scenarios.js.md +++ b/test/snapshots/markdownlint-test-scenarios.js.md @@ -3247,26 +3247,6 @@ Generated by [AVA](https://avajs.dev). 'no-bare-urls', ], }, - { - errorContext: 'https://example.com', - errorDetail: null, - errorRange: [ - 45, - 19, - ], - fixInfo: { - deleteCount: 19, - editColumn: 45, - insertText: '', - }, - lineNumber: 86, - ruleDescription: 'Bare URL used', - ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md034.md', - ruleNames: [ - 'MD034', - 'no-bare-urls', - ], - }, ], fixed: `# Detailed Results Bare URLs␊ ␊ @@ -3353,10 +3333,9 @@ Generated by [AVA](https://avajs.dev). ␊ Links bind to the innermost [link that [is-a-valid] link]() {MD034}␊ ␊ - But not if the [link [is-not-a-valid] link]() {MD034}␊ - HOWEVER this scenario could have an invalid shortcut and IS reported␊ + But not if the [link [is-not-a-valid] link](https://example.com)␊ ␊ - Escaping both inner square brackets avoids the unwanted report:␊ + Escaping both inner square brackets avoids confusion:␊ [link \\[is-not-a-valid\\] link](https://example.com)␊ `, } @@ -23985,6 +23964,26 @@ Generated by [AVA](https://avajs.dev). 'no-bare-urls', ], }, + { + errorContext: 'https://example.com', + errorDetail: null, + errorRange: [ + 43, + 19, + ], + fixInfo: { + deleteCount: 19, + editColumn: 43, + insertText: '', + }, + lineNumber: 30, + ruleDescription: 'Bare URL used', + ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md034.md', + ruleNames: [ + 'MD034', + 'no-bare-urls', + ], + }, ], fixed: `# Link test␊ ␊ @@ -24013,6 +24012,11 @@ Generated by [AVA](https://avajs.dev). | Link | Same Link | Violation |␊ |----------------------|----------------------|-----------|␊ | | | {MD034} |␊ + ␊ + This is not a bare URL: [text [undefined] text](https://example.com).␊ + This is a bare URL: [text [defined] text](). {MD034}␊ + ␊ + [defined]: https://example.com␊ `, } diff --git a/test/snapshots/markdownlint-test-scenarios.js.snap b/test/snapshots/markdownlint-test-scenarios.js.snap index b7fe7d17..1248bee8 100644 Binary files a/test/snapshots/markdownlint-test-scenarios.js.snap and b/test/snapshots/markdownlint-test-scenarios.js.snap differ