-
-
Notifications
You must be signed in to change notification settings - Fork 721
/
md054.js
125 lines (121 loc) · 4.57 KB
/
md054.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// @ts-check
"use strict";
const { addErrorContext, nextLinesRe } = require("../helpers");
const { filterByPredicate, getTokenTextByType } = require("../helpers/micromark.cjs");
const { getReferenceLinkImageData, filterByTypesCached } = require("./cache");
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;
}
return !autolinkDisallowedRe.test(destination);
};
// eslint-disable-next-line jsdoc/valid-types
/** @type import("./markdownlint").Rule */
module.exports = {
"names": [ "MD054", "link-image-style" ],
"description": "Link and image style",
"tags": [ "images", "links" ],
"parser": "micromark",
"function": (params, onError) => {
const config = params.config;
const autolink = (config.autolink === undefined) || !!config.autolink;
const inline = (config.inline === undefined) || !!config.inline;
const full = (config.full === undefined) || !!config.full;
const collapsed = (config.collapsed === undefined) || !!config.collapsed;
const shortcut = (config.shortcut === undefined) || !!config.shortcut;
const urlInline = (config.url_inline === undefined) || !!config.url_inline;
if (autolink && inline && full && collapsed && shortcut && urlInline) {
// Everything allowed, nothing to check
return;
}
const { definitions } = getReferenceLinkImageData();
const links = filterByTypesCached([ "autolink", "image", "link" ]);
for (const link of links) {
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
const title = getTokenTextByType(descendents, "resourceTitleString");
isError = !inline || (
!urlInline &&
autolink &&
!image &&
!title &&
(label === destination) &&
autolinkAble(destination)
);
} else {
// link kind is a full/collapsed/shortcut reference link
const isShortcut = !children.some((t) => t.type === "reference");
const referenceString = getTokenTextByType(descendents, "referenceString");
const isCollapsed = (referenceString === null);
const definition = definitions.get(referenceString || label);
destination = definition && definition[1];
isError = destination &&
(isShortcut ? !shortcut : (isCollapsed ? !collapsed : !full));
}
}
if (isError) {
// eslint-disable-next-line no-undef-init
let range = undefined;
let fixInfo = null;
if (startLine === endLine) {
range = [ startColumn, endColumn - startColumn ];
let insertText = null;
const canInline = (inline && label);
const canAutolink = (autolink && !image && autolinkAble(destination));
if (canInline && (urlInline || !canAutolink)) {
// Most useful form
const prefix = (image ? "!" : "");
// @ts-ignore
const escapedLabel = label.replace(/[[\]]/g, "\\$&");
const escapedDestination = destination.replace(/[()]/g, "\\$&");
insertText = `${prefix}[${escapedLabel}](${escapedDestination})`;
} else if (canAutolink) {
// Simplest form
insertText = `<${removeBackslashEscapes(destination)}>`;
}
if (insertText) {
fixInfo = {
"editColumn": range[0],
insertText,
"deleteCount": range[1]
};
}
}
addErrorContext(
onError,
startLine,
text.replace(nextLinesRe, ""),
undefined,
undefined,
range,
fixInfo
);
}
}
}
};