Refactor new MD054/link-image-style rule, add tests, update documentation (fixes #40, fixes #399).

This commit is contained in:
David Anson 2023-10-25 20:05:19 -07:00
parent 460836445c
commit 10f095c4fd
32 changed files with 3149 additions and 333 deletions

View file

@ -5,6 +5,8 @@ atx
ATX ATX
atx_closed atx_closed
ATX-style ATX-style
Autolinks
autolinks
axibase axibase
backtick backtick
backticks backticks

View file

@ -130,8 +130,8 @@ playground for learning and exploring.
- **[MD046](doc/md046.md)** *code-block-style* - Code block style - **[MD046](doc/md046.md)** *code-block-style* - Code block style
- **[MD047](doc/md047.md)** *single-trailing-newline* - Files should end with a single newline character - **[MD047](doc/md047.md)** *single-trailing-newline* - Files should end with a single newline character
- **[MD048](doc/md048.md)** *code-fence-style* - Code fence style - **[MD048](doc/md048.md)** *code-fence-style* - Code fence style
- **[MD049](doc/md049.md)** *emphasis-style* - Emphasis style should be consistent - **[MD049](doc/md049.md)** *emphasis-style* - Emphasis style
- **[MD050](doc/md050.md)** *strong-style* - Strong style should be consistent - **[MD050](doc/md050.md)** *strong-style* - Strong style
- **[MD051](doc/md051.md)** *link-fragments* - Link fragments should be valid - **[MD051](doc/md051.md)** *link-fragments* - Link fragments should be valid
- **[MD052](doc/md052.md)** *reference-links-images* - Reference links and images should use a label that is defined - **[MD052](doc/md052.md)** *reference-links-images* - Reference links and images should use a label that is defined
- **[MD053](doc/md053.md)** *link-image-reference-definitions* - Link and image reference definitions should be needed - **[MD053](doc/md053.md)** *link-image-reference-definitions* - Link and image reference definitions should be needed

View file

@ -799,7 +799,12 @@ function getReferenceLinkImageData(params) {
if (definitions.has(reference)) { if (definitions.has(reference)) {
duplicateDefinitions.push([reference, token.startLine - 1]); duplicateDefinitions.push([reference, token.startLine - 1]);
} else { } else {
definitions.set(reference, token.startLine - 1); var destinationString = null;
var parent = micromark.getTokenParentOfType(token, ["definition"]);
if (parent) {
destinationString = micromark.getTokenTextByType(micromark.filterByPredicate(parent.children), "definitionDestinationString");
}
definitions.set(reference, [token.startLine - 1, destinationString]);
} }
} }
break; break;
@ -1416,11 +1421,14 @@ function micromarkParse(markdown) {
* Filter a list of Micromark tokens by predicate. * Filter a list of Micromark tokens by predicate.
* *
* @param {Token[]} tokens Micromark tokens. * @param {Token[]} tokens Micromark tokens.
* @param {AllowedPredicate} allowed Allowed token predicate. * @param {AllowedPredicate} [allowed] Allowed token predicate.
* @param {TransformPredicate} [transformChildren] Transform predicate. * @param {TransformPredicate} [transformChildren] Transform predicate.
* @returns {Token[]} Filtered tokens. * @returns {Token[]} Filtered tokens.
*/ */
function filterByPredicate(tokens, allowed, transformChildren) { function filterByPredicate(tokens, allowed, transformChildren) {
allowed = allowed || function () {
return true;
};
var result = []; var result = [];
var queue = [{ var queue = [{
"array": tokens, "array": tokens,
@ -1525,7 +1533,7 @@ function getTokenParentOfType(token, types) {
} }
/** /**
* Get the text of a single token from a list of Micromark tokens by type. * Get the text of the first match from a list of Micromark tokens by type.
* *
* @param {Token[]} tokens Micromark tokens. * @param {Token[]} tokens Micromark tokens.
* @param {string} type Types to match. * @param {string} type Types to match.
@ -1535,7 +1543,7 @@ function getTokenTextByType(tokens, type) {
var filtered = tokens.filter(function (token) { var filtered = tokens.filter(function (token) {
return token.type === type; return token.type === type;
}); });
return filtered.length === 1 ? filtered[0].text : null; return filtered.length > 0 ? filtered[0].text : null;
} }
/** /**
@ -1655,7 +1663,7 @@ module.exports.referenceLinkImageData = function () {
module.exports.deprecatedRuleNames = ["MD002", "MD006"]; module.exports.deprecatedRuleNames = ["MD002", "MD006"];
module.exports.fixableRuleNames = ["MD004", "MD005", "MD006", "MD007", "MD009", "MD010", "MD011", "MD012", "MD014", "MD018", "MD019", "MD020", "MD021", "MD022", "MD023", "MD026", "MD027", "MD030", "MD031", "MD032", "MD034", "MD037", "MD038", "MD039", "MD044", "MD047", "MD049", "MD050", "MD051", "MD053"]; module.exports.fixableRuleNames = ["MD004", "MD005", "MD006", "MD007", "MD009", "MD010", "MD011", "MD012", "MD014", "MD018", "MD019", "MD020", "MD021", "MD022", "MD023", "MD026", "MD027", "MD030", "MD031", "MD032", "MD034", "MD037", "MD038", "MD039", "MD044", "MD047", "MD049", "MD050", "MD051", "MD053", "MD054"];
module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; module.exports.homepage = "https://github.com/DavidAnson/markdownlint";
module.exports.version = "0.31.1"; module.exports.version = "0.31.1";
@ -5048,9 +5056,7 @@ module.exports = {
// Find the "visual" end of the list // Find the "visual" end of the list
var endLine = list.endLine; var endLine = list.endLine;
var flattenedChildren = filterByPredicate(list.children, function () { var flattenedChildren = filterByPredicate(list.children);
return true;
});
var _iterator2 = _createForOfIteratorHelper(flattenedChildren.reverse()), var _iterator2 = _createForOfIteratorHelper(flattenedChildren.reverse()),
_step2; _step2;
try { try {
@ -6306,14 +6312,14 @@ var impl = function impl(params, onError, type, asterisk, underline) {
}; };
module.exports = [{ module.exports = [{
"names": ["MD049", "emphasis-style"], "names": ["MD049", "emphasis-style"],
"description": "Emphasis style should be consistent", "description": "Emphasis style",
"tags": ["emphasis"], "tags": ["emphasis"],
"function": function MD049(params, onError) { "function": function MD049(params, onError) {
return impl(params, onError, "emphasis", "*", "_", params.config.style || undefined); return impl(params, onError, "emphasis", "*", "_", params.config.style || undefined);
} }
}, { }, {
"names": ["MD050", "strong-style"], "names": ["MD050", "strong-style"],
"description": "Strong style should be consistent", "description": "Strong style",
"tags": ["emphasis"], "tags": ["emphasis"],
"function": function MD050(params, onError) { "function": function MD050(params, onError) {
return impl(params, onError, "strong", "**", "__", params.config.style || undefined); return impl(params, onError, "strong", "**", "__", params.config.style || undefined);
@ -6657,7 +6663,8 @@ module.exports = {
var definition = _step.value; var definition = _step.value;
var _definition = _slicedToArray(definition, 2), var _definition = _slicedToArray(definition, 2),
label = _definition[0], label = _definition[0],
lineIndex = _definition[1]; _definition$ = _slicedToArray(_definition[1], 1),
lineIndex = _definition$[0];
if (!ignored.has(label) && !references.has(label) && !shortcuts.has(label)) { if (!ignored.has(label) && !references.has(label) && !shortcuts.has(label)) {
var line = lines[lineIndex]; var line = lines[lineIndex];
addError(onError, lineIndex + 1, "Unused link or image reference definition: \"".concat(label, "\""), ellipsify(line), [1, line.length], singleLineDefinition(line) ? deleteFixInfo : 0); addError(onError, lineIndex + 1, "Unused link or image reference definition: \"".concat(label, "\""), ellipsify(line), [1, line.length], singleLineDefinition(line) ? deleteFixInfo : 0);
@ -6692,6 +6699,135 @@ module.exports = {
/***/ }), /***/ }),
/***/ "../lib/md054.js":
/*!***********************!*\
!*** ../lib/md054.js ***!
\***********************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
// @ts-check
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"),
addErrorContext = _require.addErrorContext,
nextLinesRe = _require.nextLinesRe;
var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"),
filterByTypes = _require2.filterByTypes,
filterByPredicate = _require2.filterByPredicate,
getTokenTextByType = _require2.getTokenTextByType;
var _require3 = __webpack_require__(/*! ./cache */ "../lib/cache.js"),
referenceLinkImageData = _require3.referenceLinkImageData;
var backslashEscapeRe = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g;
var removeBackslashEscapes = function removeBackslashEscapes(text) {
return text.replace(backslashEscapeRe, "$1");
};
var autolinkDisallowedRe = /[ <>]/;
var autolinkAble = function autolinkAble(destination) {
try {
// eslint-disable-next-line no-new
new URL(destination);
} catch (_unused) {
// Not an absolute URL
return false;
}
return !autolinkDisallowedRe.test(destination);
};
module.exports = {
"names": ["MD054", "link-image-style"],
"description": "Link and image style",
"tags": ["images", "links"],
"function": function _function(_ref, onError) {
var parsers = _ref.parsers,
config = _ref.config;
var autolink = config.autolink === undefined || !!config.autolink;
var inline = config.inline === undefined || !!config.inline;
var reference = config.reference === undefined || !!config.reference;
if (autolink && inline && reference) {
// Everything allowed, nothing to check
return;
}
var _referenceLinkImageDa = referenceLinkImageData(),
definitions = _referenceLinkImageDa.definitions;
var links = filterByTypes(parsers.micromark.tokens, ["autolink", "image", "link"]);
var _iterator = _createForOfIteratorHelper(links),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var link = _step.value;
var label = null;
var destination = null;
var children = link.children,
endColumn = link.endColumn,
endLine = link.endLine,
startColumn = link.startColumn,
startLine = link.startLine,
text = link.text,
type = link.type;
var image = type === "image";
var isError = false;
if (type === "autolink") {
// link kind is an autolink
destination = getTokenTextByType(children, "autolinkProtocol");
label = destination;
isError = !autolink;
} else {
// link type is "image" or "link"
var descendents = filterByPredicate(children);
label = getTokenTextByType(descendents, "labelText");
destination = getTokenTextByType(descendents, "resourceDestinationString");
if (destination) {
// link kind is an inline link
isError = !inline;
} else {
// link kind is a reference link
var referenceLabel = getTokenTextByType(descendents, "referenceString") || label;
var definition = definitions.get(referenceLabel);
destination = definition && definition[1];
isError = !reference && destination;
}
}
if (isError) {
var range = null;
var fixInfo = null;
if (startLine === endLine) {
range = [startColumn, endColumn - startColumn];
var insertText = null;
if (inline && label) {
// Most useful form
var prefix = image ? "!" : "";
var escapedLabel = label.replace(/[[\]]/g, "\\$&");
var escapedDestination = destination.replace(/[()]/g, "\\$&");
insertText = "".concat(prefix, "[").concat(escapedLabel, "](").concat(escapedDestination, ")");
} else if (autolink && !image && autolinkAble(destination)) {
// Simplest form
insertText = "<".concat(removeBackslashEscapes(destination), ">");
}
if (insertText) {
fixInfo = {
"editColumn": range[0],
insertText: insertText,
"deleteCount": range[1]
};
}
}
addErrorContext(onError, startLine, text.replace(nextLinesRe, ""), null, null, range, fixInfo);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
};
/***/ }),
/***/ "../lib/rules.js": /***/ "../lib/rules.js":
/*!***********************!*\ /*!***********************!*\
!*** ../lib/rules.js ***! !*** ../lib/rules.js ***!
@ -6713,7 +6849,11 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
var _require = __webpack_require__(/*! ./constants */ "../lib/constants.js"), var _require = __webpack_require__(/*! ./constants */ "../lib/constants.js"),
homepage = _require.homepage, homepage = _require.homepage,
version = _require.version; version = _require.version;
var rules = [__webpack_require__(/*! ./md001 */ "../lib/md001.js"), __webpack_require__(/*! ./md002 */ "../lib/md002.js"), __webpack_require__(/*! ./md003 */ "../lib/md003.js"), __webpack_require__(/*! ./md004 */ "../lib/md004.js"), __webpack_require__(/*! ./md005 */ "../lib/md005.js"), __webpack_require__(/*! ./md006 */ "../lib/md006.js"), __webpack_require__(/*! ./md007 */ "../lib/md007.js"), __webpack_require__(/*! ./md009 */ "../lib/md009.js"), __webpack_require__(/*! ./md010 */ "../lib/md010.js"), __webpack_require__(/*! ./md011 */ "../lib/md011.js"), __webpack_require__(/*! ./md012 */ "../lib/md012.js"), __webpack_require__(/*! ./md013 */ "../lib/md013.js"), __webpack_require__(/*! ./md014 */ "../lib/md014.js"), __webpack_require__(/*! ./md018 */ "../lib/md018.js"), __webpack_require__(/*! ./md019 */ "../lib/md019.js"), __webpack_require__(/*! ./md020 */ "../lib/md020.js"), __webpack_require__(/*! ./md021 */ "../lib/md021.js"), __webpack_require__(/*! ./md022 */ "../lib/md022.js"), __webpack_require__(/*! ./md023 */ "../lib/md023.js"), __webpack_require__(/*! ./md024 */ "../lib/md024.js"), __webpack_require__(/*! ./md025 */ "../lib/md025.js"), __webpack_require__(/*! ./md026 */ "../lib/md026.js"), __webpack_require__(/*! ./md027 */ "../lib/md027.js"), __webpack_require__(/*! ./md028 */ "../lib/md028.js"), __webpack_require__(/*! ./md029 */ "../lib/md029.js"), __webpack_require__(/*! ./md030 */ "../lib/md030.js"), __webpack_require__(/*! ./md031 */ "../lib/md031.js"), __webpack_require__(/*! ./md032 */ "../lib/md032.js"), __webpack_require__(/*! ./md033 */ "../lib/md033.js"), __webpack_require__(/*! ./md034 */ "../lib/md034.js"), __webpack_require__(/*! ./md035 */ "../lib/md035.js"), __webpack_require__(/*! ./md036 */ "../lib/md036.js"), __webpack_require__(/*! ./md037 */ "../lib/md037.js"), __webpack_require__(/*! ./md038 */ "../lib/md038.js"), __webpack_require__(/*! ./md039 */ "../lib/md039.js"), __webpack_require__(/*! ./md040 */ "../lib/md040.js"), __webpack_require__(/*! ./md041 */ "../lib/md041.js"), __webpack_require__(/*! ./md042 */ "../lib/md042.js"), __webpack_require__(/*! ./md043 */ "../lib/md043.js"), __webpack_require__(/*! ./md044 */ "../lib/md044.js"), __webpack_require__(/*! ./md045 */ "../lib/md045.js"), __webpack_require__(/*! ./md046 */ "../lib/md046.js"), __webpack_require__(/*! ./md047 */ "../lib/md047.js"), __webpack_require__(/*! ./md048 */ "../lib/md048.js")].concat(_toConsumableArray(__webpack_require__(/*! ./md049-md050 */ "../lib/md049-md050.js")), [__webpack_require__(/*! ./md051 */ "../lib/md051.js"), __webpack_require__(/*! ./md052 */ "../lib/md052.js"), __webpack_require__(/*! ./md053 */ "../lib/md053.js")]); var rules = [__webpack_require__(/*! ./md001 */ "../lib/md001.js"), __webpack_require__(/*! ./md002 */ "../lib/md002.js"), __webpack_require__(/*! ./md003 */ "../lib/md003.js"), __webpack_require__(/*! ./md004 */ "../lib/md004.js"), __webpack_require__(/*! ./md005 */ "../lib/md005.js"), __webpack_require__(/*! ./md006 */ "../lib/md006.js"), __webpack_require__(/*! ./md007 */ "../lib/md007.js"), __webpack_require__(/*! ./md009 */ "../lib/md009.js"), __webpack_require__(/*! ./md010 */ "../lib/md010.js"), __webpack_require__(/*! ./md011 */ "../lib/md011.js"), __webpack_require__(/*! ./md012 */ "../lib/md012.js"), __webpack_require__(/*! ./md013 */ "../lib/md013.js"), __webpack_require__(/*! ./md014 */ "../lib/md014.js"), __webpack_require__(/*! ./md018 */ "../lib/md018.js"), __webpack_require__(/*! ./md019 */ "../lib/md019.js"), __webpack_require__(/*! ./md020 */ "../lib/md020.js"), __webpack_require__(/*! ./md021 */ "../lib/md021.js"), __webpack_require__(/*! ./md022 */ "../lib/md022.js"), __webpack_require__(/*! ./md023 */ "../lib/md023.js"), __webpack_require__(/*! ./md024 */ "../lib/md024.js"), __webpack_require__(/*! ./md025 */ "../lib/md025.js"), __webpack_require__(/*! ./md026 */ "../lib/md026.js"), __webpack_require__(/*! ./md027 */ "../lib/md027.js"), __webpack_require__(/*! ./md028 */ "../lib/md028.js"), __webpack_require__(/*! ./md029 */ "../lib/md029.js"), __webpack_require__(/*! ./md030 */ "../lib/md030.js"), __webpack_require__(/*! ./md031 */ "../lib/md031.js"), __webpack_require__(/*! ./md032 */ "../lib/md032.js"), __webpack_require__(/*! ./md033 */ "../lib/md033.js"), __webpack_require__(/*! ./md034 */ "../lib/md034.js"), __webpack_require__(/*! ./md035 */ "../lib/md035.js"), __webpack_require__(/*! ./md036 */ "../lib/md036.js"), __webpack_require__(/*! ./md037 */ "../lib/md037.js"), __webpack_require__(/*! ./md038 */ "../lib/md038.js"), __webpack_require__(/*! ./md039 */ "../lib/md039.js"), __webpack_require__(/*! ./md040 */ "../lib/md040.js"), __webpack_require__(/*! ./md041 */ "../lib/md041.js"), __webpack_require__(/*! ./md042 */ "../lib/md042.js"), __webpack_require__(/*! ./md043 */ "../lib/md043.js"), __webpack_require__(/*! ./md044 */ "../lib/md044.js"), __webpack_require__(/*! ./md045 */ "../lib/md045.js"), __webpack_require__(/*! ./md046 */ "../lib/md046.js"), __webpack_require__(/*! ./md047 */ "../lib/md047.js"), __webpack_require__(/*! ./md048 */ "../lib/md048.js")].concat(_toConsumableArray(__webpack_require__(/*! ./md049-md050 */ "../lib/md049-md050.js")), [__webpack_require__(/*! ./md051 */ "../lib/md051.js"), __webpack_require__(/*! ./md052 */ "../lib/md052.js"), __webpack_require__(/*! ./md053 */ "../lib/md053.js"), __webpack_require__(/*! ./md054 */ "../lib/md054.js")
// md055: See https://github.com/markdownlint/markdownlint
// md056: See https://github.com/markdownlint/markdownlint
// md057: See https://github.com/markdownlint/markdownlint
]);
var _iterator = _createForOfIteratorHelper(rules), var _iterator = _createForOfIteratorHelper(rules),
_step; _step;
try { try {

View file

@ -1,11 +1,54 @@
Links and images in Markdown can provide the link destination or image source Links and images in Markdown can provide the link destination or image source at
at the time of use or can use a label to reference a definition elsewhere in the time of use or can use a label to reference a definition elsewhere in the
the document. The latter reference format is convenient for keeping paragraph document. The reference format is convenient for keeping paragraph text
text clutter-free and makes it easy to reuse the same URL in multiple places. clutter-free and makes it easy to reuse the same URL in multiple places.
This rule can be used to enforce a link or image style for the document that: By default, this rule allows all link/image styles. It is possible to disable
one or more of those styles.
- `inline`: provides the link destination or image source at the time of use Setting the `autolink` parameter to `false` disables autolinks:
- `reference`: defines the link destination or image source elsewhere to be
referenced by label ```markdown
- or `consistent`: Requires the same style be used everywhere in the document <https://example.com>
```
Setting the `inline` parameter to `false` disables inline links and images:
```markdown
[link](https://example.com)
![image](https://example.com)
```
Setting the `reference` parameter to `false` disables full, collapsed, and
shortcut reference links and images:
```markdown
[link][url]
[url][]
[url]
![image][url]
![url][]
![url]
[url]: https://example.com
```
To fix violations of this rule, change the link or image to use an allowed
style. This rule can automatically fix violations when a link or image can be
converted to the `inline` style (preferred) or a link can be converted to the
`autolink` style (which does not support images and must be an absolute URL).
This rule does not fix scenarios that require converting a link or image to the
`reference` style because that involves naming the reference and knowing where
in the document to insert it.
Rationale: Consistent formatting makes it easier to understand a document.
Autolinks are concise, but appear as URLs which can be long and confusing.
Inline links and images can include descriptive text, but take up more space in
Markdown form. Reference links and images can be easier to read and manipulate
in Markdown form, but require editing two locations.

View file

@ -2104,7 +2104,7 @@ Rationale: Consistent formatting makes it easier to understand a document.
<a name="md049"></a> <a name="md049"></a>
## `MD049` - Emphasis style should be consistent ## `MD049` - Emphasis style
Tags: `emphasis` Tags: `emphasis`
@ -2139,7 +2139,7 @@ Rationale: Consistent formatting makes it easier to understand a document.
<a name="md050"></a> <a name="md050"></a>
## `MD050` - Strong style should be consistent ## `MD050` - Strong style
Tags: `emphasis` Tags: `emphasis`
@ -2294,9 +2294,9 @@ Parameters:
Fixable: Some violations can be fixed by tooling Fixable: Some violations can be fixed by tooling
Links and images in Markdown can provide the link destination or image source Links and images in Markdown can provide the link destination or image source
at the time of use or can define it elsewhere and use a label for reference. at the time of use or can use a label to reference a definition elsewhere in
The reference format is convenient for keeping paragraph text clutter-free the document. The latter reference format is convenient for keeping paragraph
and makes it easy to reuse the same URL in multiple places. text clutter-free and makes it easy to reuse the same URL in multiple places.
Because link and image reference definitions are located separately from Because link and image reference definitions are located separately from
where they are used, there are two scenarios where a definition can be where they are used, there are two scenarios where a definition can be
@ -2320,6 +2320,77 @@ Markdown:
[//]: # (This behaves like a comment) [//]: # (This behaves like a comment)
``` ```
<a name="md054"></a>
## `MD054` - Link and image style
Tags: `images`, `links`
Aliases: `link-image-style`
Parameters:
- `autolink`: Allow autolinks (`boolean`, default `true`)
- `inline`: Allow inline links and images (`boolean`, default `true`)
- `reference`: Allow reference links and images (`boolean`, default `true`)
Fixable: Some violations can be fixed by tooling
Links and images in Markdown can provide the link destination or image source at
the time of use or can use a label to reference a definition elsewhere in the
document. The reference format is convenient for keeping paragraph text
clutter-free and makes it easy to reuse the same URL in multiple places.
By default, this rule allows all link/image styles. It is possible to disable
one or more of those styles.
Setting the `autolink` parameter to `false` disables autolinks:
```markdown
<https://example.com>
```
Setting the `inline` parameter to `false` disables inline links and images:
```markdown
[link](https://example.com)
![image](https://example.com)
```
Setting the `reference` parameter to `false` disables full, collapsed, and
shortcut reference links and images:
```markdown
[link][url]
[url][]
[url]
![image][url]
![url][]
![url]
[url]: https://example.com
```
To fix violations of this rule, change the link or image to use an allowed
style. This rule can automatically fix violations when a link or image can be
converted to the `inline` style (preferred) or a link can be converted to the
`autolink` style (which does not support images and must be an absolute URL).
This rule does not fix scenarios that require converting a link or image to the
`reference` style because that involves naming the reference and knowing where
in the document to insert it.
Rationale: Consistent formatting makes it easier to understand a document.
Autolinks are concise, but appear as URLs which can be long and confusing.
Inline links and images can include descriptive text, but take up more space in
Markdown form. Reference links and images can be easier to read and manipulate
in Markdown form, but require editing two locations.
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"no-inline-html": { "no-inline-html": {
"allowed_elements": [ "allowed_elements": [

View file

@ -1,4 +1,4 @@
# `MD049` - Emphasis style should be consistent # `MD049` - Emphasis style
Tags: `emphasis` Tags: `emphasis`

View file

@ -1,4 +1,4 @@
# `MD050` - Strong style should be consistent # `MD050` - Strong style
Tags: `emphasis` Tags: `emphasis`

View file

@ -11,9 +11,9 @@ Parameters:
Fixable: Some violations can be fixed by tooling Fixable: Some violations can be fixed by tooling
Links and images in Markdown can provide the link destination or image source Links and images in Markdown can provide the link destination or image source
at the time of use or can define it elsewhere and use a label for reference. at the time of use or can use a label to reference a definition elsewhere in
The reference format is convenient for keeping paragraph text clutter-free the document. The latter reference format is convenient for keeping paragraph
and makes it easy to reuse the same URL in multiple places. text clutter-free and makes it easy to reuse the same URL in multiple places.
Because link and image reference definitions are located separately from Because link and image reference definitions are located separately from
where they are used, there are two scenarios where a definition can be where they are used, there are two scenarios where a definition can be

68
doc/md054.md Normal file
View file

@ -0,0 +1,68 @@
# `MD054` - Link and image style
Tags: `images`, `links`
Aliases: `link-image-style`
Parameters:
- `autolink`: Allow autolinks (`boolean`, default `true`)
- `inline`: Allow inline links and images (`boolean`, default `true`)
- `reference`: Allow reference links and images (`boolean`, default `true`)
Fixable: Some violations can be fixed by tooling
Links and images in Markdown can provide the link destination or image source at
the time of use or can use a label to reference a definition elsewhere in the
document. The reference format is convenient for keeping paragraph text
clutter-free and makes it easy to reuse the same URL in multiple places.
By default, this rule allows all link/image styles. It is possible to disable
one or more of those styles.
Setting the `autolink` parameter to `false` disables autolinks:
```markdown
<https://example.com>
```
Setting the `inline` parameter to `false` disables inline links and images:
```markdown
[link](https://example.com)
![image](https://example.com)
```
Setting the `reference` parameter to `false` disables full, collapsed, and
shortcut reference links and images:
```markdown
[link][url]
[url][]
[url]
![image][url]
![url][]
![url]
[url]: https://example.com
```
To fix violations of this rule, change the link or image to use an allowed
style. This rule can automatically fix violations when a link or image can be
converted to the `inline` style (preferred) or a link can be converted to the
`autolink` style (which does not support images and must be an absolute URL).
This rule does not fix scenarios that require converting a link or image to the
`reference` style because that involves naming the reference and knowing where
in the document to insert it.
Rationale: Consistent formatting makes it easier to understand a document.
Autolinks are concise, but appear as URLs which can be long and confusing.
Inline links and images can include descriptive text, but take up more space in
Markdown form. Reference links and images can be easier to read and manipulate
in Markdown form, but require editing two locations.

View file

@ -748,7 +748,19 @@ function getReferenceLinkImageData(params) {
if (definitions.has(reference)) { if (definitions.has(reference)) {
duplicateDefinitions.push([ reference, token.startLine - 1 ]); duplicateDefinitions.push([ reference, token.startLine - 1 ]);
} else { } else {
definitions.set(reference, token.startLine - 1); let destinationString = null;
const parent =
micromark.getTokenParentOfType(token, [ "definition" ]);
if (parent) {
destinationString = micromark.getTokenTextByType(
micromark.filterByPredicate(parent.children),
"definitionDestinationString"
);
}
definitions.set(
reference,
[ token.startLine - 1, destinationString ]
);
} }
} }
break; break;

View file

@ -239,11 +239,12 @@ function micromarkParse(
* Filter a list of Micromark tokens by predicate. * Filter a list of Micromark tokens by predicate.
* *
* @param {Token[]} tokens Micromark tokens. * @param {Token[]} tokens Micromark tokens.
* @param {AllowedPredicate} allowed Allowed token predicate. * @param {AllowedPredicate} [allowed] Allowed token predicate.
* @param {TransformPredicate} [transformChildren] Transform predicate. * @param {TransformPredicate} [transformChildren] Transform predicate.
* @returns {Token[]} Filtered tokens. * @returns {Token[]} Filtered tokens.
*/ */
function filterByPredicate(tokens, allowed, transformChildren) { function filterByPredicate(tokens, allowed, transformChildren) {
allowed = allowed || (() => true);
const result = []; const result = [];
const queue = [ const queue = [
{ {
@ -353,7 +354,7 @@ function getTokenParentOfType(token, types) {
} }
/** /**
* Get the text of a single token from a list of Micromark tokens by type. * Get the text of the first match from a list of Micromark tokens by type.
* *
* @param {Token[]} tokens Micromark tokens. * @param {Token[]} tokens Micromark tokens.
* @param {string} type Types to match. * @param {string} type Types to match.
@ -361,7 +362,7 @@ function getTokenParentOfType(token, types) {
*/ */
function getTokenTextByType(tokens, type) { function getTokenTextByType(tokens, type) {
const filtered = tokens.filter((token) => token.type === type); const filtered = tokens.filter((token) => token.type === type);
return (filtered.length === 1) ? filtered[0].text : null; return (filtered.length > 0) ? filtered[0].text : null;
} }
/** /**

View file

@ -60,10 +60,7 @@ module.exports = {
// Find the "visual" end of the list // Find the "visual" end of the list
let endLine = list.endLine; let endLine = list.endLine;
const flattenedChildren = filterByPredicate( const flattenedChildren = filterByPredicate(list.children);
list.children,
() => true
);
for (const child of flattenedChildren.reverse()) { for (const child of flattenedChildren.reverse()) {
if (!nonContentTokens.has(child.type)) { if (!nonContentTokens.has(child.type)) {
endLine = child.endLine; endLine = child.endLine;

View file

@ -58,7 +58,7 @@ const impl =
module.exports = [ module.exports = [
{ {
"names": [ "MD049", "emphasis-style" ], "names": [ "MD049", "emphasis-style" ],
"description": "Emphasis style should be consistent", "description": "Emphasis style",
"tags": [ "emphasis" ], "tags": [ "emphasis" ],
"function": function MD049(params, onError) { "function": function MD049(params, onError) {
return impl( return impl(
@ -73,7 +73,7 @@ module.exports = [
}, },
{ {
"names": [ "MD050", "strong-style" ], "names": [ "MD050", "strong-style" ],
"description": "Strong style should be consistent", "description": "Strong style",
"tags": [ "emphasis" ], "tags": [ "emphasis" ],
"function": function MD050(params, onError) { "function": function MD050(params, onError) {
return impl( return impl(

View file

@ -23,7 +23,7 @@ module.exports = {
}; };
// Look for unused link references (unreferenced by any link/image) // Look for unused link references (unreferenced by any link/image)
for (const definition of definitions.entries()) { for (const definition of definitions.entries()) {
const [ label, lineIndex ] = definition; const [ label, [ lineIndex ] ] = definition;
if ( if (
!ignored.has(label) && !ignored.has(label) &&
!references.has(label) && !references.has(label) &&

View file

@ -2,92 +2,23 @@
"use strict"; "use strict";
const { addErrorContext } = require("../helpers"); const { addErrorContext, nextLinesRe } = require("../helpers");
const { filterByTypes, filterByPredicate, getTokenTextByType } = const { filterByTypes, filterByPredicate, getTokenTextByType } =
require("../helpers/micromark.cjs"); require("../helpers/micromark.cjs");
const { referenceLinkImageData } = require("./cache");
const isInlineLink = ({ children }) => children.some( const backslashEscapeRe = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g;
({ type }) => type === "resource" const removeBackslashEscapes = (text) => text.replace(backslashEscapeRe, "$1");
); const autolinkDisallowedRe = /[ <>]/;
const autolinkAble = (destination) => {
const isAutolink = ({ type }) => type === "autolink"; try {
// eslint-disable-next-line no-new
const getNestedTokenTextByType = (tokens, type) => getTokenTextByType( new URL(destination);
filterByTypes(tokens, [ type ]), } catch {
type // Not an absolute URL
); return false;
const escapeParentheses = (unescaped) => unescaped
.replaceAll("(", "\\(")
.replaceAll(")", "\\)");
const escapeSquares = (unescaped) => unescaped
.replaceAll("[", "\\[")
.replaceAll("]", "\\]");
const escapeAngles = (unescaped) => unescaped
.replaceAll("<", "\\<")
.replaceAll(">", "\\>");
const unescapeParentheses = (escaped) => escaped
.replaceAll("\\(", "(")
.replaceAll("\\)", ")");
const unescapeAngles = (escaped) => escaped
.replaceAll("\\<", "<")
.replaceAll("\\>", ">");
const referenceLinkDestination = (link, tokens) => {
const reference = getNestedTokenTextByType([ link ], "reference");
const id = reference && reference !== "[]" ?
reference.replace(/^\[/, "").replace(/\]$/, "") :
getNestedTokenTextByType([ link ], "labelText");
const definition = filterByPredicate(
filterByTypes(tokens, [ "definition" ]),
(d) => getNestedTokenTextByType([ d ], "definitionLabelString") === id
);
return getNestedTokenTextByType(definition, "definitionDestination");
};
const inlineLinkDestination = (link) => {
const text = getNestedTokenTextByType([ link ], "resourceDestination");
return text && unescapeParentheses(text);
};
const autolinkDestination = (link) => {
const text = getNestedTokenTextByType([ link ], "autolinkProtocol");
return text && unescapeAngles(text);
};
const autolinkFixInfo = (tokens, link) => {
if (isAutolink(link)) {
return null;
} }
return !autolinkDisallowedRe.test(destination);
const destination = isInlineLink(link) ?
inlineLinkDestination(link) :
referenceLinkDestination(link, tokens);
return {
"editColumn": link.startColumn,
"insertText": `<${escapeAngles(destination)}>`,
"deleteCount": link.endColumn - link.startColumn
};
};
const inlineFixInfo = (tokens, link) => {
if (isInlineLink(link)) {
return null;
}
const destination = isAutolink(link) ?
autolinkDestination(link) :
referenceLinkDestination(link, tokens);
return {
"editColumn": link.startColumn,
"insertText":
`[${escapeSquares(destination)}](${escapeParentheses(destination)})`,
"deleteCount": link.endColumn - link.startColumn
};
}; };
module.exports = { module.exports = {
@ -95,34 +26,77 @@ module.exports = {
"description": "Link and image style", "description": "Link and image style",
"tags": [ "images", "links" ], "tags": [ "images", "links" ],
"function": ({ parsers, config }, onError) => { "function": ({ parsers, config }, onError) => {
const style = String(config.style || "mixed"); const autolink = (config.autolink === undefined) || !!config.autolink;
const inline = (config.inline === undefined) || !!config.inline;
const reference = (config.reference === undefined) || !!config.reference;
if (autolink && inline && reference) {
// Everything allowed, nothing to check
return;
}
const { definitions } = referenceLinkImageData();
const links = filterByTypes( const links = filterByTypes(
parsers.micromark.tokens, parsers.micromark.tokens,
[ "autolink", "link", "image" ] [ "autolink", "image", "link" ]
); );
for (const link of links) { for (const link of links) {
const inlineLink = isInlineLink(link); let label = null;
const autolink = isAutolink(link); let destination = null;
const {
const range = [ link.startColumn, link.endColumn - link.startColumn ]; children, endColumn, endLine, startColumn, startLine, text, type
let fixInfo = null; } = link;
if (style === "autolink_only") { const image = (type === "image");
fixInfo = autolinkFixInfo(parsers.micromark.tokens, link); let isError = false;
} else if (style === "inline_only") { if (type === "autolink") {
fixInfo = inlineFixInfo(parsers.micromark.tokens, link); // link kind is an autolink
destination = getTokenTextByType(children, "autolinkProtocol");
label = destination;
isError = !autolink;
} else {
// link type is "image" or "link"
const descendents = filterByPredicate(children);
label = getTokenTextByType(descendents, "labelText");
destination =
getTokenTextByType(descendents, "resourceDestinationString");
if (destination) {
// link kind is an inline link
isError = !inline;
} else {
// link kind is a reference link
const referenceLabel =
getTokenTextByType(descendents, "referenceString") || label;
const definition = definitions.get(referenceLabel);
destination = definition && definition[1];
isError = !reference && destination;
}
} }
if (isError) {
if ( let range = null;
fixInfo || let fixInfo = null;
(style === "reference_only" && (inlineLink || autolink)) || if (startLine === endLine) {
(style === "inline_or_reference" && autolink) || range = [ startColumn, endColumn - startColumn ];
(style === "inline_or_autolink" && !(inlineLink || autolink)) || let insertText = null;
(style === "reference_or_autolink" && inlineLink) if (inline && label) {
) { // Most useful form
const prefix = (image ? "!" : "");
const escapedLabel = label.replace(/[[\]]/g, "\\$&");
const escapedDestination = destination.replace(/[()]/g, "\\$&");
insertText = `${prefix}[${escapedLabel}](${escapedDestination})`;
} else if (autolink && !image && autolinkAble(destination)) {
// Simplest form
insertText = `<${removeBackslashEscapes(destination)}>`;
}
if (insertText) {
fixInfo = {
"editColumn": range[0],
insertText,
"deleteCount": range[1]
};
}
}
addErrorContext( addErrorContext(
onError, onError,
link.startLine, startLine,
link.text, text.replace(nextLinesRe, ""),
null, null,
null, null,
range, range,

View file

@ -54,6 +54,9 @@ const rules = [
require("./md052"), require("./md052"),
require("./md053"), require("./md053"),
require("./md054") require("./md054")
// md055: See https://github.com/markdownlint/markdownlint
// md056: See https://github.com/markdownlint/markdownlint
// md057: See https://github.com/markdownlint/markdownlint
]; ];
for (const rule of rules) { for (const rule of rules) {
const name = rule.names[0].toLowerCase(); const name = rule.names[0].toLowerCase();

View file

@ -263,13 +263,13 @@
"style": "consistent" "style": "consistent"
}, },
// MD049/emphasis-style - Emphasis style should be consistent // MD049/emphasis-style - Emphasis style
"MD049": { "MD049": {
// Emphasis style // Emphasis style
"style": "consistent" "style": "consistent"
}, },
// MD050/strong-style - Strong style should be consistent // MD050/strong-style - Strong style
"MD050": { "MD050": {
// Strong style // Strong style
"style": "consistent" "style": "consistent"
@ -290,5 +290,15 @@
"ignored_definitions": [ "ignored_definitions": [
"//" "//"
] ]
},
// MD054/link-image-style - Link and image style
"MD054": {
// Allow autolinks
"autolink": true,
// Allow inline links and images
"inline": true,
// Allow reference links and images
"reference": true
} }
} }

View file

@ -238,12 +238,12 @@ MD048:
# Code fence style # Code fence style
style: "consistent" style: "consistent"
# MD049/emphasis-style - Emphasis style should be consistent # MD049/emphasis-style - Emphasis style
MD049: MD049:
# Emphasis style # Emphasis style
style: "consistent" style: "consistent"
# MD050/strong-style - Strong style should be consistent # MD050/strong-style - Strong style
MD050: MD050:
# Strong style # Strong style
style: "consistent" style: "consistent"
@ -261,3 +261,12 @@ MD053:
# Ignored definitions # Ignored definitions
ignored_definitions: ignored_definitions:
- "//" - "//"
# MD054/link-image-style - Link and image style
MD054:
# Allow autolinks
autolink: true
# Allow inline links and images
inline: true
# Allow reference links and images
reference: true

View file

@ -521,19 +521,20 @@ for (const rule of rules) {
break; break;
case "MD054": case "MD054":
scheme.properties = { scheme.properties = {
"style": { "autolink": {
"description": "Link or image style should be consistent", "description": "Allow autolinks",
"type": "string", "type": "boolean",
"enum": [ "default": true
"mixed", },
"autolink_only", "inline": {
"inline_only", "description": "Allow inline links and images",
"reference_only", "type": "boolean",
"inline_or_reference", "default": true
"inline_or_autolink", },
"reference_or_autolink" "reference": {
], "description": "Allow reference links and images",
"default": "mixed" "type": "boolean",
"default": true
} }
}; };
break; break;

View file

@ -881,7 +881,7 @@
"$ref": "#/properties/MD048" "$ref": "#/properties/MD048"
}, },
"MD049": { "MD049": {
"description": "MD049/emphasis-style - Emphasis style should be consistent", "description": "MD049/emphasis-style - Emphasis style",
"type": [ "type": [
"boolean", "boolean",
"object" "object"
@ -905,7 +905,7 @@
"$ref": "#/properties/MD049" "$ref": "#/properties/MD049"
}, },
"MD050": { "MD050": {
"description": "MD050/strong-style - Strong style should be consistent", "description": "MD050/strong-style - Strong style",
"type": [ "type": [
"boolean", "boolean",
"object" "object"
@ -979,6 +979,35 @@
"link-image-reference-definitions": { "link-image-reference-definitions": {
"$ref": "#/properties/MD053" "$ref": "#/properties/MD053"
}, },
"MD054": {
"description": "MD054/link-image-style - Link and image style",
"type": [
"boolean",
"object"
],
"default": true,
"properties": {
"autolink": {
"description": "Allow autolinks",
"type": "boolean",
"default": true
},
"inline": {
"description": "Allow inline links and images",
"type": "boolean",
"default": true
},
"reference": {
"description": "Allow reference links and images",
"type": "boolean",
"default": true
}
},
"additionalProperties": false
},
"link-image-style": {
"$ref": "#/properties/MD054"
},
"headings": { "headings": {
"description": "headings - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043", "description": "headings - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043",
"type": "boolean", "type": "boolean",
@ -1015,7 +1044,7 @@
"default": true "default": true
}, },
"links": { "links": {
"description": "links - MD011, MD034, MD039, MD042, MD051, MD052, MD053", "description": "links - MD011, MD034, MD039, MD042, MD051, MD052, MD053, MD054",
"type": "boolean", "type": "boolean",
"default": true "default": true
}, },
@ -1095,7 +1124,7 @@
"default": true "default": true
}, },
"images": { "images": {
"description": "images - MD045, MD052, MD053", "description": "images - MD045, MD052, MD053, MD054",
"type": "boolean", "type": "boolean",
"default": true "default": true
} }

View file

@ -100,6 +100,11 @@ Strong **with** different style {MD050}
[unused]: link-destination [unused]: link-destination
{MD053:100} {MD053:100}
[text][url] {MD054}
<!-- markdownlint-disable-next-line MD053 -->
[url]: https://example.com/page
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"first-heading-h1": true, "first-heading-h1": true,
"ul-start-left": true, "ul-start-left": true,
@ -114,6 +119,9 @@ Strong **with** different style {MD050}
"names": [ "names": [
"markdownlint" "markdownlint"
] ]
},
"link-image-style": {
"reference": false
} }
} --> } -->

View file

@ -1,20 +1,78 @@
# Autolink Link Style # Link Style autolink_only
Text [url](https://example.com) text {MD054} Text [url](https://example.com) text {MD054}
Text ![url](https://example.com) text {MD054} Text ![url](https://example.com) text {MD054}
Text [url] text {MD054}
Text ![url] text {MD054} Text [url](<https://example.com>) text {MD054}
Text ![url](<https://example.com>) text {MD054}
Text [url](https://example.com "title") text {MD054}
Text ![url](https://example.com "title") text {MD054}
Text {MD054} [url](https://example.com
"title") text
Text {MD054} ![url](https://example.com
"title") text
Text [text][url] text {MD054} Text [text][url] text {MD054}
Text ![text][url] text {MD054} Text ![text][url] text {MD054}
Text <https://example.com> text
Text [url][] text {MD054} Text [url][] text {MD054}
[url]: https://example.com Text ![url][] text {MD054}
Text [url] text {MD054}
Text ![url] text {MD054}
Text <https://example.com> text
[url]: https://example.com "title"
[undefined]
Text [url](https://example.com/embedded\3backslash) text {MD054}
Text [url](https://example.com/backslash\[escape) text {MD054}
Text [embedded-backslash] text {MD054}
Text [backslash-escape] text {MD054}
Text <https://example.com/embedded\3backslash> text
Text <https://example.com/backslash[no-escape> text
[embedded-backslash]: https://example.com/embedded\3backslash
[backslash-escape]: https://example.com/backslash\[escape
Text [url](<https://example.com/embedded space>) text {MD054}
Text [url](<https://example.com/embedded)paren>) text {MD054}
Text [url](https://example.com/\(parens\)) text {MD054}
Text [url](https://example.com/pa(re(ns))) text {MD054}
Text [url](relative/path) text {MD054}
Text [url](#fragment) text {MD054}
Text <https://example.com/pa)re(ns> text
Text [url](https://example.com/an>g<le>) text {MD054}
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"no-bare-urls": false, "link-fragments": false,
"link-image-reference-definitions": false, "link-image-reference-definitions": false,
"link-image-style": { "link-image-style": {
"style": "autolink_only" "inline": false,
"reference": false
} }
} --> } -->

View file

@ -0,0 +1,77 @@
# Link Style autolink_or_inline
Text [url](https://example.com) text
Text ![url](https://example.com) text
Text [url](<https://example.com>) text
Text ![url](<https://example.com>) text
Text [url](https://example.com "title") text
Text ![url](https://example.com "title") text
Text [url](https://example.com
"title") text
Text ![url](https://example.com
"title") text
Text [text][url] text {MD054}
Text ![text][url] text {MD054}
Text [url][] text {MD054}
Text ![url][] text {MD054}
Text [url] text {MD054}
Text ![url] text {MD054}
Text <https://example.com> text
[url]: https://example.com "title"
[undefined]
Text [url](https://example.com/embedded\3backslash) text
Text [url](https://example.com/backslash\[escape) text
Text [embedded-backslash] text {MD054}
Text [backslash-escape] text {MD054}
Text <https://example.com/embedded\3backslash> text
Text <https://example.com/backslash[no-escape> text
[embedded-backslash]: https://example.com/embedded\3backslash
[backslash-escape]: https://example.com/backslash\[escape
Text [url](<https://example.com/embedded space>) text
Text [url](<https://example.com/embedded)paren>) text
Text [url](https://example.com/\(parens\)) text
Text [url](https://example.com/pa(re(ns))) text
Text [url](relative/path) text
Text [url](#fragment) text
Text <https://example.com/pa)re(ns> text
Text [url](https://example.com/an>g<le>) text
<!-- markdownlint-configure-file {
"link-fragments": false,
"link-image-reference-definitions": false,
"link-image-style": {
"reference": false
}
} -->

View file

@ -0,0 +1,76 @@
# Link Style autolink_or_reference
Text [url](https://example.com) text {MD054}
Text ![url](https://example.com) text {MD054}
Text [url](<https://example.com>) text {MD054}
Text ![url](<https://example.com>) text {MD054}
Text [url](https://example.com "title") text {MD054}
Text ![url](https://example.com "title") text {MD054}
Text {MD054} [url](https://example.com
"title") text
Text {MD054} ![url](https://example.com
"title") text
Text [text][url] text
Text ![text][url] text
Text [url][] text
Text ![url][] text
Text [url] text
Text ![url] text
Text <https://example.com> text
[url]: https://example.com "title"
[undefined]
Text [url](https://example.com/embedded\3backslash) text {MD054}
Text [url](https://example.com/backslash\[escape) text {MD054}
Text [embedded-backslash] text
Text [backslash-escape] text
Text <https://example.com/embedded\3backslash> text
Text <https://example.com/backslash[no-escape> text
[embedded-backslash]: https://example.com/embedded\3backslash
[backslash-escape]: https://example.com/backslash\[escape
Text [url](<https://example.com/embedded space>) text {MD054}
Text [url](<https://example.com/embedded)paren>) text {MD054}
Text [url](https://example.com/\(parens\)) text {MD054}
Text [url](https://example.com/pa(re(ns))) text {MD054}
Text [url](relative/path) text {MD054}
Text [url](#fragment) text {MD054}
Text <https://example.com/pa)re(ns> text
Text [url](https://example.com/an>g<le>) text {MD054}
<!-- markdownlint-configure-file {
"link-fragments": false,
"link-image-style": {
"inline": false
}
} -->

View file

@ -1,20 +1,78 @@
# Inline Link Style # Link Style inline_only
Text [url](https://example.com) text Text [url](https://example.com) text
Text ![url](https://example.com) text Text ![url](https://example.com) text
Text [url] {MD054} text
Text ![url] {MD054} text Text [url](<https://example.com>) text
Text [text][url] {MD054} text
Text ![text][url] {MD054} text Text ![url](<https://example.com>) text
Text <https://example.com> {MD054} text
Text [url](https://example.com "title") text
Text ![url](https://example.com "title") text
Text [url](https://example.com
"title") text
Text ![url](https://example.com
"title") text
Text [text][url] text {MD054}
Text ![text][url] text {MD054}
Text [url][] text {MD054} Text [url][] text {MD054}
[url]: https://example.com Text ![url][] text {MD054}
Text [url] text {MD054}
Text ![url] text {MD054}
Text <https://example.com> text {MD054}
[url]: https://example.com "title"
[undefined]
Text [url](https://example.com/embedded\3backslash) text
Text [url](https://example.com/backslash\[escape) text
Text [embedded-backslash] text {MD054}
Text [backslash-escape] text {MD054}
Text <https://example.com/embedded\3backslash> text {MD054}
Text <https://example.com/backslash[no-escape> text {MD054}
[embedded-backslash]: https://example.com/embedded\3backslash
[backslash-escape]: https://example.com/backslash\[escape
Text [url](<https://example.com/embedded space>) text
Text [url](<https://example.com/embedded)paren>) text
Text [url](https://example.com/\(parens\)) text
Text [url](https://example.com/pa(re(ns))) text
Text [url](relative/path) text
Text [url](#fragment) text
Text <https://example.com/pa)re(ns> text {MD054}
Text [url](https://example.com/an>g<le>) text
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"no-bare-urls": false, "link-fragments": false,
"link-image-reference-definitions": false, "link-image-reference-definitions": false,
"link-image-style": { "link-image-style": {
"style": "inline_only" "autolink": false,
"reference": false
} }
} --> } -->

View file

@ -1,20 +0,0 @@
# Inline Link Style
Text [url](https://example.com) text
Text ![url](https://example.com) text
Text [url] {MD054} text
Text ![url] {MD054} text
Text [text][url] {MD054} text
Text ![text][url] {MD054} text
Text <https://example.com> text
Text [url][] text {MD054}
[url]: https://example.com
<!-- markdownlint-configure-file {
"no-bare-urls": false,
"link-image-reference-definitions": false,
"link-image-style": {
"style": "inline_or_autolink"
}
} -->

View file

@ -1,20 +1,76 @@
# Inline Link Style # Link Style inline_or_reference
Text [url](https://example.com) text Text [url](https://example.com) text
Text ![url](https://example.com) text Text ![url](https://example.com) text
Text [url] text
Text ![url] text Text [url](<https://example.com>) text
Text ![url](<https://example.com>) text
Text [url](https://example.com "title") text
Text ![url](https://example.com "title") text
Text [url](https://example.com
"title") text
Text ![url](https://example.com
"title") text
Text [text][url] text Text [text][url] text
Text ![text][url] text Text ![text][url] text
Text <https://example.com> {MD054} text
Text [url][] text Text [url][] text
[url]: https://example.com Text ![url][] text
Text [url] text
Text ![url] text
Text <https://example.com> text {MD054}
[url]: https://example.com "title"
[undefined]
Text [url](https://example.com/embedded\3backslash) text
Text [url](https://example.com/backslash\[escape) text
Text [embedded-backslash] text
Text [backslash-escape] text
Text <https://example.com/embedded\3backslash> text {MD054}
Text <https://example.com/backslash[no-escape> text {MD054}
[embedded-backslash]: https://example.com/embedded\3backslash
[backslash-escape]: https://example.com/backslash\[escape
Text [url](<https://example.com/embedded space>) text
Text [url](<https://example.com/embedded)paren>) text
Text [url](https://example.com/\(parens\)) text
Text [url](https://example.com/pa(re(ns))) text
Text [url](relative/path) text
Text [url](#fragment) text
Text <https://example.com/pa)re(ns> text {MD054}
Text [url](https://example.com/an>g<le>) text
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"no-bare-urls": false, "link-fragments": false,
"link-image-reference-definitions": false,
"link-image-style": { "link-image-style": {
"style": "inline_or_reference" "autolink": false
} }
} --> } -->

View file

@ -1,18 +1,77 @@
# Reference Link Style # Link Style reference_only
Text [url](https://example.com) text {MD054}
Text ![url](https://example.com) text {MD054}
Text [url](<https://example.com>) text {MD054}
Text ![url](<https://example.com>) text {MD054}
Text [url](https://example.com "title") text {MD054}
Text ![url](https://example.com "title") text {MD054}
Text {MD054} [url](https://example.com
"title") text
Text {MD054} ![url](https://example.com
"title") text
Text [url {MD054}](https://example.com) text
Text ![url {MD054}](https://example.com) text
Text [url] text
Text ![url] text
Text [text][url] text Text [text][url] text
Text ![text][url] text Text ![text][url] text
Text <https://example.com> {MD054} text
Text [url][] text Text [url][] text
[url]: https://example.com Text ![url][] text
Text [url] text
Text ![url] text
Text <https://example.com> text {MD054}
[url]: https://example.com "title"
[undefined]
Text [url](https://example.com/embedded\3backslash) text {MD054}
Text [url](https://example.com/backslash\[escape) text {MD054}
Text [embedded-backslash] text
Text [backslash-escape] text
Text <https://example.com/embedded\3backslash> text {MD054}
Text <https://example.com/backslash[no-escape> text {MD054}
[embedded-backslash]: https://example.com/embedded\3backslash
[backslash-escape]: https://example.com/backslash\[escape
Text [url](<https://example.com/embedded space>) text {MD054}
Text [url](<https://example.com/embedded)paren>) text {MD054}
Text [url](https://example.com/\(parens\)) text {MD054}
Text [url](https://example.com/pa(re(ns))) text {MD054}
Text [url](relative/path) text {MD054}
Text [url](#fragment) text {MD054}
Text <https://example.com/pa)re(ns> text {MD054}
Text [url](https://example.com/an>g<le>) text {MD054}
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"link-fragments": false,
"link-image-style": { "link-image-style": {
"style": "reference_only" "autolink": false,
"inline": false
} }
} --> } -->

View file

@ -1,20 +0,0 @@
# Inline Link Style
Text [url](https://example.com) text {MD054}
Text ![url](https://example.com) text {MD054}
Text [url] text
Text ![url] text
Text [text][url] text
Text ![text][url] text
Text <https://example.com> text
Text [url][] text
[url]: https://example.com
<!-- markdownlint-configure-file {
"no-bare-urls": false,
"link-image-reference-definitions": false,
"link-image-style": {
"style": "reference_or_autolink"
}
} -->

View file

@ -474,7 +474,7 @@ test("styleAll", (t) => new Promise((resolve) => {
"MD042": [ 81 ], "MD042": [ 81 ],
"MD045": [ 85 ], "MD045": [ 85 ],
"MD046": [ 49, 73, 77 ], "MD046": [ 49, 73, 77 ],
"MD047": [ 120 ], "MD047": [ 128 ],
"MD048": [ 77 ], "MD048": [ 77 ],
"MD049": [ 90 ], "MD049": [ 90 ],
"MD050": [ 94 ], "MD050": [ 94 ],
@ -523,7 +523,7 @@ test("styleRelaxed", (t) => new Promise((resolve) => {
"MD042": [ 81 ], "MD042": [ 81 ],
"MD045": [ 85 ], "MD045": [ 85 ],
"MD046": [ 49, 73, 77 ], "MD046": [ 49, 73, 77 ],
"MD047": [ 120 ], "MD047": [ 128 ],
"MD048": [ 77 ], "MD048": [ 77 ],
"MD049": [ 90 ], "MD049": [ 90 ],
"MD050": [ 94 ], "MD050": [ 94 ],

File diff suppressed because it is too large Load diff