mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-09-21 21:30:47 +02:00
Add new MD054/link-image-style rule (in-progress PR, no generated files).
This commit is contained in:
parent
20b8af5054
commit
460836445c
14 changed files with 296 additions and 11 deletions
|
@ -135,6 +135,7 @@ playground for learning and exploring.
|
||||||
- **[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
|
||||||
|
- **[MD054](doc/md054.md)** *link-image-style* - Link and image style
|
||||||
|
|
||||||
<!-- markdownlint-restore -->
|
<!-- markdownlint-restore -->
|
||||||
|
|
||||||
|
@ -176,11 +177,12 @@ rules at once.
|
||||||
`MD043`
|
`MD043`
|
||||||
- **`hr`** - `MD035`
|
- **`hr`** - `MD035`
|
||||||
- **`html`** - `MD033`
|
- **`html`** - `MD033`
|
||||||
- **`images`** - `MD045`, `MD052`, `MD053`
|
- **`images`** - `MD045`, `MD052`, `MD053`, `MD054`
|
||||||
- **`indentation`** - `MD005`, `MD006`, `MD007`, `MD027`
|
- **`indentation`** - `MD005`, `MD006`, `MD007`, `MD027`
|
||||||
- **`language`** - `MD040`
|
- **`language`** - `MD040`
|
||||||
- **`line_length`** - `MD013`
|
- **`line_length`** - `MD013`
|
||||||
- **`links`** - `MD011`, `MD034`, `MD039`, `MD042`, `MD051`, `MD052`, `MD053`
|
- **`links`** - `MD011`, `MD034`, `MD039`, `MD042`, `MD051`, `MD052`, `MD053`,
|
||||||
|
`MD054`
|
||||||
- **`ol`** - `MD029`, `MD030`, `MD032`
|
- **`ol`** - `MD029`, `MD030`, `MD032`
|
||||||
- **`spaces`** - `MD018`, `MD019`, `MD020`, `MD021`, `MD023`
|
- **`spaces`** - `MD018`, `MD019`, `MD020`, `MD021`, `MD023`
|
||||||
- **`spelling`** - `MD044`
|
- **`spelling`** - `MD044`
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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
|
||||||
|
|
11
doc-build/md054.md
Normal file
11
doc-build/md054.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
This rule can be used to enforce a link or image style for the document that:
|
||||||
|
|
||||||
|
- `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
|
|
@ -8,7 +8,8 @@ module.exports.fixableRuleNames = [
|
||||||
"MD011", "MD012", "MD014", "MD018", "MD019", "MD020",
|
"MD011", "MD012", "MD014", "MD018", "MD019", "MD020",
|
||||||
"MD021", "MD022", "MD023", "MD026", "MD027", "MD030",
|
"MD021", "MD022", "MD023", "MD026", "MD027", "MD030",
|
||||||
"MD031", "MD032", "MD034", "MD037", "MD038", "MD039",
|
"MD031", "MD032", "MD034", "MD037", "MD038", "MD039",
|
||||||
"MD044", "MD047", "MD049", "MD050", "MD051", "MD053"
|
"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";
|
||||||
|
|
134
lib/md054.js
Normal file
134
lib/md054.js
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { addErrorContext } = require("../helpers");
|
||||||
|
const { filterByTypes, filterByPredicate, getTokenTextByType } =
|
||||||
|
require("../helpers/micromark.cjs");
|
||||||
|
|
||||||
|
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 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 = {
|
||||||
|
"names": [ "MD054", "link-image-style" ],
|
||||||
|
"description": "Link and image style",
|
||||||
|
"tags": [ "images", "links" ],
|
||||||
|
"function": ({ parsers, config }, onError) => {
|
||||||
|
const style = String(config.style || "mixed");
|
||||||
|
const links = filterByTypes(
|
||||||
|
parsers.micromark.tokens,
|
||||||
|
[ "autolink", "link", "image" ]
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
range,
|
||||||
|
fixInfo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -52,7 +52,8 @@ const rules = [
|
||||||
...require("./md049-md050"),
|
...require("./md049-md050"),
|
||||||
require("./md051"),
|
require("./md051"),
|
||||||
require("./md052"),
|
require("./md052"),
|
||||||
require("./md053")
|
require("./md053"),
|
||||||
|
require("./md054")
|
||||||
];
|
];
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
const name = rule.names[0].toLowerCase();
|
const name = rule.names[0].toLowerCase();
|
||||||
|
|
|
@ -519,6 +519,24 @@ for (const rule of rules) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
break;
|
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"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
custom = false;
|
custom = false;
|
||||||
break;
|
break;
|
||||||
|
|
20
test/link-style-autolink-only.md
Normal file
20
test/link-style-autolink-only.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Autolink Link Style
|
||||||
|
|
||||||
|
Text [url](https://example.com) text {MD054}
|
||||||
|
Text  text {MD054}
|
||||||
|
Text [url] text {MD054}
|
||||||
|
Text ![url] text {MD054}
|
||||||
|
Text [text][url] text {MD054}
|
||||||
|
Text ![text][url] text {MD054}
|
||||||
|
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": "autolink_only"
|
||||||
|
}
|
||||||
|
} -->
|
20
test/link-style-inline-only.md
Normal file
20
test/link-style-inline-only.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Inline Link Style
|
||||||
|
|
||||||
|
Text [url](https://example.com) text
|
||||||
|
Text  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][] text {MD054}
|
||||||
|
|
||||||
|
[url]: https://example.com
|
||||||
|
|
||||||
|
<!-- markdownlint-configure-file {
|
||||||
|
"no-bare-urls": false,
|
||||||
|
"link-image-reference-definitions": false,
|
||||||
|
"link-image-style": {
|
||||||
|
"style": "inline_only"
|
||||||
|
}
|
||||||
|
} -->
|
20
test/link-style-inline-or-autolink.md
Normal file
20
test/link-style-inline-or-autolink.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Inline Link Style
|
||||||
|
|
||||||
|
Text [url](https://example.com) text
|
||||||
|
Text  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"
|
||||||
|
}
|
||||||
|
} -->
|
20
test/link-style-inline-or-reference.md
Normal file
20
test/link-style-inline-or-reference.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Inline Link Style
|
||||||
|
|
||||||
|
Text [url](https://example.com) text
|
||||||
|
Text  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
|
||||||
|
|
||||||
|
<!-- markdownlint-configure-file {
|
||||||
|
"no-bare-urls": false,
|
||||||
|
"link-image-reference-definitions": false,
|
||||||
|
"link-image-style": {
|
||||||
|
"style": "inline_or_reference"
|
||||||
|
}
|
||||||
|
} -->
|
18
test/link-style-reference-only.md
Normal file
18
test/link-style-reference-only.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Reference Link Style
|
||||||
|
|
||||||
|
Text [url {MD054}](https://example.com) text
|
||||||
|
Text  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
|
||||||
|
|
||||||
|
<!-- markdownlint-configure-file {
|
||||||
|
"link-image-style": {
|
||||||
|
"style": "reference_only"
|
||||||
|
}
|
||||||
|
} -->
|
20
test/link-style-reference-or-autolink.md
Normal file
20
test/link-style-reference-or-autolink.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Inline Link Style
|
||||||
|
|
||||||
|
Text [url](https://example.com) text {MD054}
|
||||||
|
Text  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"
|
||||||
|
}
|
||||||
|
} -->
|
|
@ -82,7 +82,7 @@ test("projectFiles", (t) => {
|
||||||
"schema/*.md"
|
"schema/*.md"
|
||||||
]))
|
]))
|
||||||
.then((files) => {
|
.then((files) => {
|
||||||
t.is(files.length, 58);
|
t.is(files.length, 59);
|
||||||
const options = {
|
const options = {
|
||||||
files,
|
files,
|
||||||
"config": require("../.markdownlint.json")
|
"config": require("../.markdownlint.json")
|
||||||
|
@ -839,7 +839,7 @@ test("customFileSystemAsync", (t) => new Promise((resolve) => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test("readme", async(t) => {
|
test("readme", async(t) => {
|
||||||
t.plan(124);
|
t.plan(126);
|
||||||
const tagToRules = {};
|
const tagToRules = {};
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
for (const tag of rule.tags) {
|
for (const tag of rule.tags) {
|
||||||
|
@ -914,7 +914,7 @@ test("readme", async(t) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("validateJsonUsingConfigSchemaStrict", async(t) => {
|
test("validateJsonUsingConfigSchemaStrict", async(t) => {
|
||||||
t.plan(162);
|
t.plan(168);
|
||||||
const { addSchema, validate } =
|
const { addSchema, validate } =
|
||||||
// eslint-disable-next-line n/file-extension-in-import
|
// eslint-disable-next-line n/file-extension-in-import
|
||||||
await import("@hyperjump/json-schema/draft-07");
|
await import("@hyperjump/json-schema/draft-07");
|
||||||
|
@ -1046,7 +1046,7 @@ test("validateConfigExampleJson", async(t) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("allBuiltInRulesHaveValidUrl", (t) => {
|
test("allBuiltInRulesHaveValidUrl", (t) => {
|
||||||
t.plan(147);
|
t.plan(150);
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
t.truthy(rule.information);
|
t.truthy(rule.information);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue