mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-24 01:40:13 +01:00
This commit is contained in:
parent
460836445c
commit
10f095c4fd
32 changed files with 3149 additions and 333 deletions
180
lib/md054.js
180
lib/md054.js
|
|
@ -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 (
|
||||
fixInfo ||
|
||||
(style === "reference_only" && (inlineLink || autolink)) ||
|
||||
(style === "inline_or_reference" && autolink) ||
|
||||
(style === "inline_or_autolink" && !(inlineLink || autolink)) ||
|
||||
(style === "reference_or_autolink" && inlineLink)
|
||||
) {
|
||||
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]
|
||||
};
|
||||
}
|
||||
}
|
||||
addErrorContext(
|
||||
onError,
|
||||
link.startLine,
|
||||
link.text,
|
||||
startLine,
|
||||
text.replace(nextLinesRe, ""),
|
||||
null,
|
||||
null,
|
||||
range,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue