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
|
||||
- **[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
|
||||
- **[MD054](doc/md054.md)** *link-image-style* - Link and image style
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
|
@ -176,11 +177,12 @@ rules at once.
|
|||
`MD043`
|
||||
- **`hr`** - `MD035`
|
||||
- **`html`** - `MD033`
|
||||
- **`images`** - `MD045`, `MD052`, `MD053`
|
||||
- **`images`** - `MD045`, `MD052`, `MD053`, `MD054`
|
||||
- **`indentation`** - `MD005`, `MD006`, `MD007`, `MD027`
|
||||
- **`language`** - `MD040`
|
||||
- **`line_length`** - `MD013`
|
||||
- **`links`** - `MD011`, `MD034`, `MD039`, `MD042`, `MD051`, `MD052`, `MD053`
|
||||
- **`links`** - `MD011`, `MD034`, `MD039`, `MD042`, `MD051`, `MD052`, `MD053`,
|
||||
`MD054`
|
||||
- **`ol`** - `MD029`, `MD030`, `MD032`
|
||||
- **`spaces`** - `MD018`, `MD019`, `MD020`, `MD021`, `MD023`
|
||||
- **`spelling`** - `MD044`
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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
|
||||
|
|
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",
|
||||
"MD021", "MD022", "MD023", "MD026", "MD027", "MD030",
|
||||
"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.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("./md051"),
|
||||
require("./md052"),
|
||||
require("./md053")
|
||||
require("./md053"),
|
||||
require("./md054")
|
||||
];
|
||||
for (const rule of rules) {
|
||||
const name = rule.names[0].toLowerCase();
|
||||
|
|
|
@ -519,6 +519,24 @@ 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"
|
||||
}
|
||||
};
|
||||
break;
|
||||
default:
|
||||
custom = false;
|
||||
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"
|
||||
]))
|
||||
.then((files) => {
|
||||
t.is(files.length, 58);
|
||||
t.is(files.length, 59);
|
||||
const options = {
|
||||
files,
|
||||
"config": require("../.markdownlint.json")
|
||||
|
@ -839,7 +839,7 @@ test("customFileSystemAsync", (t) => new Promise((resolve) => {
|
|||
}));
|
||||
|
||||
test("readme", async(t) => {
|
||||
t.plan(124);
|
||||
t.plan(126);
|
||||
const tagToRules = {};
|
||||
for (const rule of rules) {
|
||||
for (const tag of rule.tags) {
|
||||
|
@ -914,7 +914,7 @@ test("readme", async(t) => {
|
|||
});
|
||||
|
||||
test("validateJsonUsingConfigSchemaStrict", async(t) => {
|
||||
t.plan(162);
|
||||
t.plan(168);
|
||||
const { addSchema, validate } =
|
||||
// eslint-disable-next-line n/file-extension-in-import
|
||||
await import("@hyperjump/json-schema/draft-07");
|
||||
|
@ -1046,7 +1046,7 @@ test("validateConfigExampleJson", async(t) => {
|
|||
});
|
||||
|
||||
test("allBuiltInRulesHaveValidUrl", (t) => {
|
||||
t.plan(147);
|
||||
t.plan(150);
|
||||
for (const rule of rules) {
|
||||
// @ts-ignore
|
||||
t.truthy(rule.information);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue