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_closed
ATX-style
Autolinks
autolinks
axibase
backtick
backticks

View file

@ -130,8 +130,8 @@ playground for learning and exploring.
- **[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
- **[MD048](doc/md048.md)** *code-fence-style* - Code fence style
- **[MD049](doc/md049.md)** *emphasis-style* - Emphasis style should be consistent
- **[MD050](doc/md050.md)** *strong-style* - Strong style should be consistent
- **[MD049](doc/md049.md)** *emphasis-style* - Emphasis style
- **[MD050](doc/md050.md)** *strong-style* - Strong style
- **[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
- **[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)) {
duplicateDefinitions.push([reference, token.startLine - 1]);
} 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;
@ -1416,11 +1421,14 @@ function micromarkParse(markdown) {
* Filter a list of Micromark tokens by predicate.
*
* @param {Token[]} tokens Micromark tokens.
* @param {AllowedPredicate} allowed Allowed token predicate.
* @param {AllowedPredicate} [allowed] Allowed token predicate.
* @param {TransformPredicate} [transformChildren] Transform predicate.
* @returns {Token[]} Filtered tokens.
*/
function filterByPredicate(tokens, allowed, transformChildren) {
allowed = allowed || function () {
return true;
};
var result = [];
var queue = [{
"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 {string} type Types to match.
@ -1535,7 +1543,7 @@ function getTokenTextByType(tokens, type) {
var filtered = tokens.filter(function (token) {
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.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.version = "0.31.1";
@ -5048,9 +5056,7 @@ module.exports = {
// Find the "visual" end of the list
var endLine = list.endLine;
var flattenedChildren = filterByPredicate(list.children, function () {
return true;
});
var flattenedChildren = filterByPredicate(list.children);
var _iterator2 = _createForOfIteratorHelper(flattenedChildren.reverse()),
_step2;
try {
@ -6306,14 +6312,14 @@ var impl = function impl(params, onError, type, asterisk, underline) {
};
module.exports = [{
"names": ["MD049", "emphasis-style"],
"description": "Emphasis style should be consistent",
"description": "Emphasis style",
"tags": ["emphasis"],
"function": function MD049(params, onError) {
return impl(params, onError, "emphasis", "*", "_", params.config.style || undefined);
}
}, {
"names": ["MD050", "strong-style"],
"description": "Strong style should be consistent",
"description": "Strong style",
"tags": ["emphasis"],
"function": function MD050(params, onError) {
return impl(params, onError, "strong", "**", "__", params.config.style || undefined);
@ -6657,7 +6663,8 @@ module.exports = {
var definition = _step.value;
var _definition = _slicedToArray(definition, 2),
label = _definition[0],
lineIndex = _definition[1];
_definition$ = _slicedToArray(_definition[1], 1),
lineIndex = _definition$[0];
if (!ignored.has(label) && !references.has(label) && !shortcuts.has(label)) {
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);
@ -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 ***!
@ -6713,7 +6849,11 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
var _require = __webpack_require__(/*! ./constants */ "../lib/constants.js"),
homepage = _require.homepage,
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),
_step;
try {

View file

@ -1,11 +1,54 @@
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 latter reference format is convenient for keeping paragraph
text clutter-free and makes it easy to reuse the same URL in multiple places.
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.
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
- `reference`: defines the link destination or image source elsewhere to be
referenced by label
- or `consistent`: Requires the same style be used everywhere in the document
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

@ -2104,7 +2104,7 @@ Rationale: Consistent formatting makes it easier to understand a document.
<a name="md049"></a>
## `MD049` - Emphasis style should be consistent
## `MD049` - Emphasis style
Tags: `emphasis`
@ -2139,7 +2139,7 @@ Rationale: Consistent formatting makes it easier to understand a document.
<a name="md050"></a>
## `MD050` - Strong style should be consistent
## `MD050` - Strong style
Tags: `emphasis`
@ -2294,9 +2294,9 @@ Parameters:
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 define it elsewhere and use a label for reference.
The reference format is convenient for keeping paragraph text clutter-free
and makes it easy to reuse the same URL in multiple places.
at the time of use or can use a label to reference a definition elsewhere in
the document. The latter reference format is convenient for keeping paragraph
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
where they are used, there are two scenarios where a definition can be
@ -2320,6 +2320,77 @@ Markdown:
[//]: # (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 {
"no-inline-html": {
"allowed_elements": [

View file

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

View file

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

View file

@ -11,9 +11,9 @@ Parameters:
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 define it elsewhere and use a label for reference.
The reference format is convenient for keeping paragraph text clutter-free
and makes it easy to reuse the same URL in multiple places.
at the time of use or can use a label to reference a definition elsewhere in
the document. The latter reference format is convenient for keeping paragraph
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
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)) {
duplicateDefinitions.push([ reference, token.startLine - 1 ]);
} 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;

View file

@ -239,11 +239,12 @@ function micromarkParse(
* Filter a list of Micromark tokens by predicate.
*
* @param {Token[]} tokens Micromark tokens.
* @param {AllowedPredicate} allowed Allowed token predicate.
* @param {AllowedPredicate} [allowed] Allowed token predicate.
* @param {TransformPredicate} [transformChildren] Transform predicate.
* @returns {Token[]} Filtered tokens.
*/
function filterByPredicate(tokens, allowed, transformChildren) {
allowed = allowed || (() => true);
const result = [];
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 {string} type Types to match.
@ -361,7 +362,7 @@ function getTokenParentOfType(token, types) {
*/
function getTokenTextByType(tokens, 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
let endLine = list.endLine;
const flattenedChildren = filterByPredicate(
list.children,
() => true
);
const flattenedChildren = filterByPredicate(list.children);
for (const child of flattenedChildren.reverse()) {
if (!nonContentTokens.has(child.type)) {
endLine = child.endLine;

View file

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

View file

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

View file

@ -2,92 +2,23 @@
"use strict";
const { addErrorContext } = require("../helpers");
const { addErrorContext, nextLinesRe } = require("../helpers");
const { filterByTypes, filterByPredicate, getTokenTextByType } =
require("../helpers/micromark.cjs");
const { referenceLinkImageData } = require("./cache");
const isInlineLink = ({ children }) => children.some(
({ type }) => type === "resource"
);
const isAutolink = ({ type }) => type === "autolink";
const getNestedTokenTextByType = (tokens, type) => getTokenTextByType(
filterByTypes(tokens, [ type ]),
type
);
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;
const backslashEscapeRe = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g;
const removeBackslashEscapes = (text) => text.replace(backslashEscapeRe, "$1");
const autolinkDisallowedRe = /[ <>]/;
const autolinkAble = (destination) => {
try {
// eslint-disable-next-line no-new
new URL(destination);
} catch {
// Not an absolute URL
return false;
}
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
};
return !autolinkDisallowedRe.test(destination);
};
module.exports = {
@ -95,34 +26,77 @@ module.exports = {
"description": "Link and image style",
"tags": [ "images", "links" ],
"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(
parsers.micromark.tokens,
[ "autolink", "link", "image" ]
[ "autolink", "image", "link" ]
);
for (const link of links) {
const inlineLink = isInlineLink(link);
const autolink = isAutolink(link);
const range = [ link.startColumn, link.endColumn - link.startColumn ];
let fixInfo = null;
if (style === "autolink_only") {
fixInfo = autolinkFixInfo(parsers.micromark.tokens, link);
} else if (style === "inline_only") {
fixInfo = inlineFixInfo(parsers.micromark.tokens, link);
let label = null;
let destination = null;
const {
children, endColumn, endLine, startColumn, startLine, text, type
} = link;
const image = (type === "image");
let 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"
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) {
let range = null;
let fixInfo = null;
if (startLine === endLine) {
range = [ startColumn, endColumn - startColumn ];
let insertText = null;
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]
};
}
}
if (
fixInfo ||
(style === "reference_only" && (inlineLink || autolink)) ||
(style === "inline_or_reference" && autolink) ||
(style === "inline_or_autolink" && !(inlineLink || autolink)) ||
(style === "reference_or_autolink" && inlineLink)
) {
addErrorContext(
onError,
link.startLine,
link.text,
startLine,
text.replace(nextLinesRe, ""),
null,
null,
range,

View file

@ -54,6 +54,9 @@ const rules = [
require("./md052"),
require("./md053"),
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) {
const name = rule.names[0].toLowerCase();

View file

@ -263,13 +263,13 @@
"style": "consistent"
},
// MD049/emphasis-style - Emphasis style should be consistent
// MD049/emphasis-style - Emphasis style
"MD049": {
// Emphasis style
"style": "consistent"
},
// MD050/strong-style - Strong style should be consistent
// MD050/strong-style - Strong style
"MD050": {
// Strong style
"style": "consistent"
@ -290,5 +290,15 @@
"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
style: "consistent"
# MD049/emphasis-style - Emphasis style should be consistent
# MD049/emphasis-style - Emphasis style
MD049:
# Emphasis style
style: "consistent"
# MD050/strong-style - Strong style should be consistent
# MD050/strong-style - Strong style
MD050:
# Strong style
style: "consistent"
@ -261,3 +261,12 @@ MD053:
# 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;
case "MD054":
scheme.properties = {
"style": {
"description": "Link or image style should be consistent",
"type": "string",
"enum": [
"mixed",
"autolink_only",
"inline_only",
"reference_only",
"inline_or_reference",
"inline_or_autolink",
"reference_or_autolink"
],
"default": "mixed"
"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
}
};
break;

View file

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

View file

@ -100,6 +100,11 @@ Strong **with** different style {MD050}
[unused]: link-destination
{MD053:100}
[text][url] {MD054}
<!-- markdownlint-disable-next-line MD053 -->
[url]: https://example.com/page
<!-- markdownlint-configure-file {
"first-heading-h1": true,
"ul-start-left": true,
@ -114,6 +119,9 @@ Strong **with** different style {MD050}
"names": [
"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] 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 <https://example.com> text
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 {
"no-bare-urls": false,
"link-fragments": false,
"link-image-reference-definitions": false,
"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] {MD054} text
Text ![url] {MD054} text
Text [text][url] {MD054} text
Text ![text][url] {MD054} text
Text <https://example.com> {MD054} 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}
[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 {
"no-bare-urls": false,
"link-fragments": false,
"link-image-reference-definitions": false,
"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] 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 <https://example.com> {MD054} 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 {
"no-bare-urls": false,
"link-image-reference-definitions": false,
"link-fragments": false,
"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 <https://example.com> {MD054} 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 {
"link-fragments": false,
"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 ],
"MD045": [ 85 ],
"MD046": [ 49, 73, 77 ],
"MD047": [ 120 ],
"MD047": [ 128 ],
"MD048": [ 77 ],
"MD049": [ 90 ],
"MD050": [ 94 ],
@ -523,7 +523,7 @@ test("styleRelaxed", (t) => new Promise((resolve) => {
"MD042": [ 81 ],
"MD045": [ 85 ],
"MD046": [ 49, 73, 77 ],
"MD047": [ 120 ],
"MD047": [ 128 ],
"MD048": [ 77 ],
"MD049": [ 90 ],
"MD050": [ 94 ],

File diff suppressed because it is too large Load diff