markdownlint 0.35.0 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +72 -35
- package/demo/markdownlint-browser.js +1259 -1215
- package/doc/CustomRules.md +55 -249
- package/doc/Rules.md +36 -29
- package/doc/md003.md +2 -2
- package/doc/md004.md +4 -3
- package/doc/md035.md +3 -8
- package/doc/md046.md +2 -2
- package/doc/md048.md +3 -2
- package/doc/md049.md +4 -3
- package/doc/md050.md +4 -4
- package/doc/md051.md +11 -3
- package/doc/md055.md +3 -2
- package/helpers/README.md +2 -19
- package/helpers/helpers.js +87 -347
- package/helpers/micromark-helpers.cjs +296 -0
- package/helpers/micromark-parse.cjs +313 -0
- package/helpers/package.json +5 -2
- package/helpers/shared.js +6 -0
- package/helpers/test.cjs +28 -0
- package/lib/cache.js +3 -3
- package/lib/configuration-strict.d.ts +120 -106
- package/lib/constants.js +1 -1
- package/lib/markdownit.cjs +172 -0
- package/lib/markdownlint.d.ts +41 -3
- package/lib/markdownlint.js +153 -115
- package/lib/md001.js +1 -1
- package/lib/md003.js +1 -1
- package/lib/md004.js +3 -3
- package/lib/md007.js +3 -3
- package/lib/md009.js +1 -1
- package/lib/md010.js +18 -14
- package/lib/md011.js +24 -23
- package/lib/md012.js +1 -1
- package/lib/md013.js +1 -1
- package/lib/md014.js +5 -9
- package/lib/md018.js +1 -1
- package/lib/md019-md021.js +1 -1
- package/lib/md020.js +2 -2
- package/lib/md022.js +9 -15
- package/lib/md024.js +1 -1
- package/lib/md025.js +1 -1
- package/lib/md027.js +2 -2
- package/lib/md029.js +3 -3
- package/lib/md031.js +8 -6
- package/lib/md032.js +27 -15
- package/lib/md033.js +1 -1
- package/lib/md034.js +21 -29
- package/lib/md036.js +18 -24
- package/lib/md037.js +1 -1
- package/lib/md038.js +9 -14
- package/lib/md039.js +13 -15
- package/lib/md040.js +11 -13
- package/lib/md041.js +4 -4
- package/lib/md042.js +2 -6
- package/lib/md043.js +1 -1
- package/lib/md044.js +31 -28
- package/lib/md045.js +2 -2
- package/lib/md048.js +28 -17
- package/lib/md049-md050.js +20 -5
- package/lib/md051.js +16 -18
- package/lib/md053.js +5 -4
- package/lib/md054.js +11 -13
- package/lib/md055.js +39 -46
- package/lib/md056.js +31 -36
- package/lib/md058.js +29 -14
- package/package.json +14 -13
- package/schema/.markdownlint.jsonc +55 -52
- package/schema/.markdownlint.yaml +54 -52
- package/schema/markdownlint-config-schema-strict.json +132 -110
- package/schema/markdownlint-config-schema.json +132 -110
- package/helpers/micromark.cjs +0 -544
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { flatTokensSymbol, htmlFlowSymbol } = require("./shared.js");
|
|
6
|
+
|
|
7
|
+
/** @typedef {import("markdownlint-micromark").TokenType} TokenType */
|
|
8
|
+
/** @typedef {import("../lib/markdownlint.js").MicromarkToken} Token */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Determines if a Micromark token is within an htmlFlow type.
|
|
12
|
+
*
|
|
13
|
+
* @param {Token} token Micromark token.
|
|
14
|
+
* @returns {boolean} True iff the token is within an htmlFlow type.
|
|
15
|
+
*/
|
|
16
|
+
function inHtmlFlow(token) {
|
|
17
|
+
return Boolean(token[htmlFlowSymbol]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns whether a token is an htmlFlow type containing an HTML comment.
|
|
22
|
+
*
|
|
23
|
+
* @param {Token} token Micromark token.
|
|
24
|
+
* @returns {boolean} True iff token is htmlFlow containing a comment.
|
|
25
|
+
*/
|
|
26
|
+
function isHtmlFlowComment(token) {
|
|
27
|
+
const { text, type } = token;
|
|
28
|
+
if (
|
|
29
|
+
(type === "htmlFlow") &&
|
|
30
|
+
text.startsWith("<!--") &&
|
|
31
|
+
text.endsWith("-->")
|
|
32
|
+
) {
|
|
33
|
+
const comment = text.slice(4, -3);
|
|
34
|
+
return (
|
|
35
|
+
!comment.startsWith(">") &&
|
|
36
|
+
!comment.startsWith("->") &&
|
|
37
|
+
!comment.endsWith("-")
|
|
38
|
+
// The following condition from the CommonMark specification is commented
|
|
39
|
+
// to avoid parsing HTML comments that include "--" because that is NOT a
|
|
40
|
+
// condition of the HTML specification.
|
|
41
|
+
// https://spec.commonmark.org/0.30/#raw-html
|
|
42
|
+
// https://html.spec.whatwg.org/multipage/syntax.html#comments
|
|
43
|
+
// && !comment.includes("--")
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Adds a range of numbers to a set.
|
|
51
|
+
*
|
|
52
|
+
* @param {Set<number>} set Set of numbers.
|
|
53
|
+
* @param {number} start Starting number.
|
|
54
|
+
* @param {number} end Ending number.
|
|
55
|
+
* @returns {void}
|
|
56
|
+
*/
|
|
57
|
+
function addRangeToSet(set, start, end) {
|
|
58
|
+
for (let i = start; i <= end; i++) {
|
|
59
|
+
set.add(i);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @callback AllowedPredicate
|
|
65
|
+
* @param {Token} token Micromark token.
|
|
66
|
+
* @returns {boolean} True iff allowed.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @callback TransformPredicate
|
|
71
|
+
* @param {Token} token Micromark token.
|
|
72
|
+
* @returns {Token[]} Child tokens.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Filter a list of Micromark tokens by predicate.
|
|
77
|
+
*
|
|
78
|
+
* @param {Token[]} tokens Micromark tokens.
|
|
79
|
+
* @param {AllowedPredicate} [allowed] Allowed token predicate.
|
|
80
|
+
* @param {TransformPredicate} [transformChildren] Transform predicate.
|
|
81
|
+
* @returns {Token[]} Filtered tokens.
|
|
82
|
+
*/
|
|
83
|
+
function filterByPredicate(tokens, allowed, transformChildren) {
|
|
84
|
+
allowed = allowed || (() => true);
|
|
85
|
+
const result = [];
|
|
86
|
+
const queue = [
|
|
87
|
+
{
|
|
88
|
+
"array": tokens,
|
|
89
|
+
"index": 0
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
while (queue.length > 0) {
|
|
93
|
+
const current = queue[queue.length - 1];
|
|
94
|
+
const { array, index } = current;
|
|
95
|
+
if (index < array.length) {
|
|
96
|
+
const token = array[current.index++];
|
|
97
|
+
if (allowed(token)) {
|
|
98
|
+
result.push(token);
|
|
99
|
+
}
|
|
100
|
+
const { children } = token;
|
|
101
|
+
if (children.length > 0) {
|
|
102
|
+
const transformed =
|
|
103
|
+
transformChildren ? transformChildren(token) : children;
|
|
104
|
+
queue.push(
|
|
105
|
+
{
|
|
106
|
+
"array": transformed,
|
|
107
|
+
"index": 0
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
queue.pop();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Filter a list of Micromark tokens by type.
|
|
120
|
+
*
|
|
121
|
+
* @param {Token[]} tokens Micromark tokens.
|
|
122
|
+
* @param {TokenType[]} types Types to allow.
|
|
123
|
+
* @param {boolean} [htmlFlow] Whether to include htmlFlow content.
|
|
124
|
+
* @returns {Token[]} Filtered tokens.
|
|
125
|
+
*/
|
|
126
|
+
function filterByTypes(tokens, types, htmlFlow) {
|
|
127
|
+
const predicate = (token) => types.includes(token.type) && (htmlFlow || !inHtmlFlow(token));
|
|
128
|
+
const flatTokens = tokens[flatTokensSymbol];
|
|
129
|
+
if (flatTokens) {
|
|
130
|
+
return flatTokens.filter(predicate);
|
|
131
|
+
}
|
|
132
|
+
return filterByPredicate(tokens, predicate);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Gets the blockquote prefix text (if any) for the specified line number.
|
|
137
|
+
*
|
|
138
|
+
* @param {Token[]} tokens Micromark tokens.
|
|
139
|
+
* @param {number} lineNumber Line number to examine.
|
|
140
|
+
* @param {number} [count] Number of times to repeat.
|
|
141
|
+
* @returns {string} Blockquote prefix text.
|
|
142
|
+
*/
|
|
143
|
+
function getBlockQuotePrefixText(tokens, lineNumber, count = 1) {
|
|
144
|
+
return filterByTypes(tokens, [ "blockQuotePrefix", "linePrefix" ])
|
|
145
|
+
.filter((prefix) => prefix.startLine === lineNumber)
|
|
146
|
+
.map((prefix) => prefix.text)
|
|
147
|
+
.join("")
|
|
148
|
+
.trimEnd()
|
|
149
|
+
// eslint-disable-next-line unicorn/prefer-spread
|
|
150
|
+
.concat("\n")
|
|
151
|
+
.repeat(count);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Gets a list of nested Micromark token descendants by type path.
|
|
156
|
+
*
|
|
157
|
+
* @param {Token|Token[]} parent Micromark token parent or parents.
|
|
158
|
+
* @param {(TokenType|TokenType[])[]} typePath Micromark token type path.
|
|
159
|
+
* @returns {Token[]} Micromark token descendants.
|
|
160
|
+
*/
|
|
161
|
+
function getDescendantsByType(parent, typePath) {
|
|
162
|
+
let tokens = Array.isArray(parent) ? parent : [ parent ];
|
|
163
|
+
for (const type of typePath) {
|
|
164
|
+
const predicate = (token) => Array.isArray(type) ? type.includes(token.type) : (type === token.type);
|
|
165
|
+
tokens = tokens.flatMap((t) => t.children.filter(predicate));
|
|
166
|
+
}
|
|
167
|
+
return tokens;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Gets the heading level of a Micromark heading tokan.
|
|
172
|
+
*
|
|
173
|
+
* @param {Token} heading Micromark heading token.
|
|
174
|
+
* @returns {number} Heading level.
|
|
175
|
+
*/
|
|
176
|
+
function getHeadingLevel(heading) {
|
|
177
|
+
let level = 1;
|
|
178
|
+
const headingSequence = heading.children.find(
|
|
179
|
+
(child) => [ "atxHeadingSequence", "setextHeadingLine" ].includes(child.type)
|
|
180
|
+
);
|
|
181
|
+
// @ts-ignore
|
|
182
|
+
const { text } = headingSequence;
|
|
183
|
+
if (text[0] === "#") {
|
|
184
|
+
level = Math.min(text.length, 6);
|
|
185
|
+
} else if (text[0] === "-") {
|
|
186
|
+
level = 2;
|
|
187
|
+
}
|
|
188
|
+
return level;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Gets the heading style of a Micromark heading tokan.
|
|
193
|
+
*
|
|
194
|
+
* @param {Token} heading Micromark heading token.
|
|
195
|
+
* @returns {"atx" | "atx_closed" | "setext"} Heading style.
|
|
196
|
+
*/
|
|
197
|
+
function getHeadingStyle(heading) {
|
|
198
|
+
if (heading.type === "setextHeading") {
|
|
199
|
+
return "setext";
|
|
200
|
+
}
|
|
201
|
+
const atxHeadingSequenceLength = heading.children.filter(
|
|
202
|
+
(child) => child.type === "atxHeadingSequence"
|
|
203
|
+
).length;
|
|
204
|
+
if (atxHeadingSequenceLength === 1) {
|
|
205
|
+
return "atx";
|
|
206
|
+
}
|
|
207
|
+
return "atx_closed";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Gets the heading text of a Micromark heading token.
|
|
212
|
+
*
|
|
213
|
+
* @param {Token} heading Micromark heading token.
|
|
214
|
+
* @returns {string} Heading text.
|
|
215
|
+
*/
|
|
216
|
+
function getHeadingText(heading) {
|
|
217
|
+
const headingTexts = getDescendantsByType(heading, [ [ "atxHeadingText", "setextHeadingText" ] ]);
|
|
218
|
+
return headingTexts[0]?.text.replace(/[\r\n]+/g, " ") || "";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* HTML tag information.
|
|
223
|
+
*
|
|
224
|
+
* @typedef {Object} HtmlTagInfo
|
|
225
|
+
* @property {boolean} close True iff close tag.
|
|
226
|
+
* @property {string} name Tag name.
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Gets information about the tag in an HTML token.
|
|
231
|
+
*
|
|
232
|
+
* @param {Token} token Micromark token.
|
|
233
|
+
* @returns {HtmlTagInfo | null} HTML tag information.
|
|
234
|
+
*/
|
|
235
|
+
function getHtmlTagInfo(token) {
|
|
236
|
+
const htmlTagNameRe = /^<([^!>][^/\s>]*)/;
|
|
237
|
+
if (token.type === "htmlText") {
|
|
238
|
+
const match = htmlTagNameRe.exec(token.text);
|
|
239
|
+
if (match) {
|
|
240
|
+
const name = match[1];
|
|
241
|
+
const close = name.startsWith("/");
|
|
242
|
+
return {
|
|
243
|
+
close,
|
|
244
|
+
"name": close ? name.slice(1) : name
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Gets the nearest parent of the specified type for a Micromark token.
|
|
253
|
+
*
|
|
254
|
+
* @param {Token} token Micromark token.
|
|
255
|
+
* @param {TokenType[]} types Types to allow.
|
|
256
|
+
* @returns {Token | null} Parent token.
|
|
257
|
+
*/
|
|
258
|
+
function getParentOfType(token, types) {
|
|
259
|
+
/** @type {Token | null} */
|
|
260
|
+
let current = token;
|
|
261
|
+
while ((current = current.parent) && !types.includes(current.type)) {
|
|
262
|
+
// Empty
|
|
263
|
+
}
|
|
264
|
+
return current;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Set containing token types that do not contain content.
|
|
269
|
+
*
|
|
270
|
+
* @type {Set<TokenType>}
|
|
271
|
+
*/
|
|
272
|
+
const nonContentTokens = new Set([
|
|
273
|
+
"blockQuoteMarker",
|
|
274
|
+
"blockQuotePrefix",
|
|
275
|
+
"blockQuotePrefixWhitespace",
|
|
276
|
+
"lineEnding",
|
|
277
|
+
"lineEndingBlank",
|
|
278
|
+
"linePrefix",
|
|
279
|
+
"listItemIndent"
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
addRangeToSet,
|
|
284
|
+
filterByPredicate,
|
|
285
|
+
filterByTypes,
|
|
286
|
+
getBlockQuotePrefixText,
|
|
287
|
+
getDescendantsByType,
|
|
288
|
+
getHeadingLevel,
|
|
289
|
+
getHeadingStyle,
|
|
290
|
+
getHeadingText,
|
|
291
|
+
getHtmlTagInfo,
|
|
292
|
+
getParentOfType,
|
|
293
|
+
inHtmlFlow,
|
|
294
|
+
isHtmlFlowComment,
|
|
295
|
+
nonContentTokens
|
|
296
|
+
};
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const micromark = require("markdownlint-micromark");
|
|
6
|
+
const { isHtmlFlowComment } = require("./micromark-helpers.cjs");
|
|
7
|
+
const { flatTokensSymbol, htmlFlowSymbol, newLineRe } = require("./shared.js");
|
|
8
|
+
|
|
9
|
+
/** @typedef {import("markdownlint-micromark").Construct} Construct */
|
|
10
|
+
/** @typedef {import("markdownlint-micromark").Event} Event */
|
|
11
|
+
/** @typedef {import("markdownlint-micromark").ParseOptions} MicromarkParseOptions */
|
|
12
|
+
/** @typedef {import("markdownlint-micromark").State} State */
|
|
13
|
+
/** @typedef {import("markdownlint-micromark").Token} Token */
|
|
14
|
+
/** @typedef {import("markdownlint-micromark").Tokenizer} Tokenizer */
|
|
15
|
+
/** @typedef {import("../lib/markdownlint.js").MicromarkToken} MicromarkToken */
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse options.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {Object} ParseOptions
|
|
21
|
+
* @property {boolean} [freezeTokens] Whether to freeze output Tokens.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parses a Markdown document and returns Micromark events.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} markdown Markdown document.
|
|
28
|
+
* @param {MicromarkParseOptions} [micromarkParseOptions] Options for micromark.
|
|
29
|
+
* @returns {Event[]} Micromark events.
|
|
30
|
+
*/
|
|
31
|
+
function getEvents(
|
|
32
|
+
markdown,
|
|
33
|
+
micromarkParseOptions = {}
|
|
34
|
+
) {
|
|
35
|
+
// Customize extensions list to add useful extensions
|
|
36
|
+
const extensions = [
|
|
37
|
+
micromark.directive(),
|
|
38
|
+
micromark.gfmAutolinkLiteral(),
|
|
39
|
+
micromark.gfmFootnote(),
|
|
40
|
+
micromark.gfmTable(),
|
|
41
|
+
micromark.math(),
|
|
42
|
+
...(micromarkParseOptions.extensions || [])
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// // Shim labelEnd to identify undefined link labels
|
|
46
|
+
/** @type {Event[][]} */
|
|
47
|
+
const artificialEventLists = [];
|
|
48
|
+
/** @type {Construct} */
|
|
49
|
+
const labelEnd =
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
micromark.labelEnd;
|
|
52
|
+
const tokenizeOriginal = labelEnd.tokenize;
|
|
53
|
+
|
|
54
|
+
/** @type {Tokenizer} */
|
|
55
|
+
function tokenizeShim(effects, okOriginal, nokOriginal) {
|
|
56
|
+
// eslint-disable-next-line consistent-this, unicorn/no-this-assignment, no-invalid-this
|
|
57
|
+
const tokenizeContext = this;
|
|
58
|
+
const events = tokenizeContext.events;
|
|
59
|
+
|
|
60
|
+
/** @type {State} */
|
|
61
|
+
const nokShim = (code) => {
|
|
62
|
+
// Find start of label (image or link)
|
|
63
|
+
let indexStart = events.length;
|
|
64
|
+
while (--indexStart >= 0) {
|
|
65
|
+
const event = events[indexStart];
|
|
66
|
+
const [ kind, token ] = event;
|
|
67
|
+
if (kind === "enter") {
|
|
68
|
+
const { type } = token;
|
|
69
|
+
if ((type === "labelImage") || (type === "labelLink")) {
|
|
70
|
+
// Found it
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If found...
|
|
77
|
+
if (indexStart >= 0) {
|
|
78
|
+
// Create artificial enter/exit events and replicate all data/lineEnding events within
|
|
79
|
+
const eventStart = events[indexStart];
|
|
80
|
+
const [ , eventStartToken ] = eventStart;
|
|
81
|
+
const eventEnd = events[events.length - 1];
|
|
82
|
+
const [ , eventEndToken ] = eventEnd;
|
|
83
|
+
/** @type {Token} */
|
|
84
|
+
const undefinedReferenceType = {
|
|
85
|
+
"type": "undefinedReferenceShortcut",
|
|
86
|
+
"start": eventStartToken.start,
|
|
87
|
+
"end": eventEndToken.end
|
|
88
|
+
};
|
|
89
|
+
/** @type {Token} */
|
|
90
|
+
const undefinedReference = {
|
|
91
|
+
"type": "undefinedReference",
|
|
92
|
+
"start": eventStartToken.start,
|
|
93
|
+
"end": eventEndToken.end
|
|
94
|
+
};
|
|
95
|
+
const eventsToReplicate = events
|
|
96
|
+
.slice(indexStart)
|
|
97
|
+
.filter((event) => {
|
|
98
|
+
const [ , eventToken ] = event;
|
|
99
|
+
const { type } = eventToken;
|
|
100
|
+
return (type === "data") || (type === "lineEnding");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Determine the type of the undefined reference
|
|
104
|
+
const previousUndefinedEvent = (artificialEventLists.length > 0) && artificialEventLists[artificialEventLists.length - 1][0];
|
|
105
|
+
const previousUndefinedToken = previousUndefinedEvent && previousUndefinedEvent[1];
|
|
106
|
+
if (
|
|
107
|
+
previousUndefinedToken &&
|
|
108
|
+
(previousUndefinedToken.end.line === undefinedReferenceType.start.line) &&
|
|
109
|
+
(previousUndefinedToken.end.column === undefinedReferenceType.start.column)
|
|
110
|
+
) {
|
|
111
|
+
// Previous undefined reference event is immediately before this one
|
|
112
|
+
if (eventsToReplicate.length === 0) {
|
|
113
|
+
// The pair represent a collapsed reference (ex: [...][])
|
|
114
|
+
previousUndefinedToken.type = "undefinedReferenceCollapsed";
|
|
115
|
+
previousUndefinedToken.end = eventEndToken.end;
|
|
116
|
+
} else {
|
|
117
|
+
// The pair represent a full reference (ex: [...][...])
|
|
118
|
+
undefinedReferenceType.type = "undefinedReferenceFull";
|
|
119
|
+
undefinedReferenceType.start = previousUndefinedToken.start;
|
|
120
|
+
artificialEventLists.pop();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Create artificial event list and replicate content
|
|
125
|
+
const text = eventsToReplicate
|
|
126
|
+
.filter((event) => event[0] === "enter")
|
|
127
|
+
.map((event) => tokenizeContext.sliceSerialize(event[1]))
|
|
128
|
+
.join("")
|
|
129
|
+
.trim();
|
|
130
|
+
if ((text.length > 0) && !text.includes("]")) {
|
|
131
|
+
/** @type {Event[]} */
|
|
132
|
+
const artificialEvents = [];
|
|
133
|
+
artificialEvents.push(
|
|
134
|
+
[ "enter", undefinedReferenceType, tokenizeContext ],
|
|
135
|
+
[ "enter", undefinedReference, tokenizeContext ]
|
|
136
|
+
);
|
|
137
|
+
for (const event of eventsToReplicate) {
|
|
138
|
+
const [ kind, token ] = event;
|
|
139
|
+
// Copy token because the current object will get modified by the parser
|
|
140
|
+
artificialEvents.push([ kind, { ...token }, tokenizeContext ]);
|
|
141
|
+
}
|
|
142
|
+
artificialEvents.push(
|
|
143
|
+
[ "exit", undefinedReference, tokenizeContext ],
|
|
144
|
+
[ "exit", undefinedReferenceType, tokenizeContext ]
|
|
145
|
+
);
|
|
146
|
+
artificialEventLists.push(artificialEvents);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Continue with original behavior
|
|
151
|
+
return nokOriginal(code);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Shim nok handler of labelEnd's tokenize
|
|
155
|
+
return tokenizeOriginal.call(tokenizeContext, effects, okOriginal, nokShim);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// Shim labelEnd behavior to detect undefined references
|
|
160
|
+
labelEnd.tokenize = tokenizeShim;
|
|
161
|
+
|
|
162
|
+
// Use micromark to parse document into Events
|
|
163
|
+
const encoding = undefined;
|
|
164
|
+
const eol = true;
|
|
165
|
+
const parseContext = micromark.parse({ ...micromarkParseOptions, extensions });
|
|
166
|
+
const chunks = micromark.preprocess()(markdown, encoding, eol);
|
|
167
|
+
const events = micromark.postprocess(parseContext.document().write(chunks));
|
|
168
|
+
|
|
169
|
+
// Append artificial events and return all events
|
|
170
|
+
// eslint-disable-next-line unicorn/prefer-spread
|
|
171
|
+
return events.concat(...artificialEventLists);
|
|
172
|
+
} finally {
|
|
173
|
+
// Restore shimmed labelEnd behavior
|
|
174
|
+
labelEnd.tokenize = tokenizeOriginal;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Parses a Markdown document and returns micromark tokens (internal).
|
|
180
|
+
*
|
|
181
|
+
* @param {string} markdown Markdown document.
|
|
182
|
+
* @param {ParseOptions} [parseOptions] Options.
|
|
183
|
+
* @param {MicromarkParseOptions} [micromarkParseOptions] Options for micromark.
|
|
184
|
+
* @param {number} [lineDelta] Offset for start/end line.
|
|
185
|
+
* @param {MicromarkToken} [ancestor] Parent of top-most tokens.
|
|
186
|
+
* @returns {MicromarkToken[]} Micromark tokens.
|
|
187
|
+
*/
|
|
188
|
+
function parseInternal(
|
|
189
|
+
markdown,
|
|
190
|
+
parseOptions = {},
|
|
191
|
+
micromarkParseOptions = {},
|
|
192
|
+
lineDelta = 0,
|
|
193
|
+
ancestor = undefined
|
|
194
|
+
) {
|
|
195
|
+
// Get options
|
|
196
|
+
const freezeTokens = Boolean(parseOptions.freezeTokens);
|
|
197
|
+
|
|
198
|
+
// Use micromark to parse document into Events
|
|
199
|
+
const events = getEvents(markdown, micromarkParseOptions);
|
|
200
|
+
|
|
201
|
+
// Create Token objects
|
|
202
|
+
const document = [];
|
|
203
|
+
let flatTokens = [];
|
|
204
|
+
/** @type {MicromarkToken} */
|
|
205
|
+
const root = {
|
|
206
|
+
"type": "data",
|
|
207
|
+
"startLine": -1,
|
|
208
|
+
"startColumn": -1,
|
|
209
|
+
"endLine": -1,
|
|
210
|
+
"endColumn": -1,
|
|
211
|
+
"text": "ROOT",
|
|
212
|
+
"children": document,
|
|
213
|
+
"parent": null
|
|
214
|
+
};
|
|
215
|
+
const history = [ root ];
|
|
216
|
+
let current = root;
|
|
217
|
+
// eslint-disable-next-line jsdoc/valid-types
|
|
218
|
+
/** @type MicromarkParseOptions | null */
|
|
219
|
+
let reparseOptions = null;
|
|
220
|
+
let lines = null;
|
|
221
|
+
let skipHtmlFlowChildren = false;
|
|
222
|
+
for (const event of events) {
|
|
223
|
+
const [ kind, token, context ] = event;
|
|
224
|
+
const { type, start, end } = token;
|
|
225
|
+
const { "column": startColumn, "line": startLine } = start;
|
|
226
|
+
const { "column": endColumn, "line": endLine } = end;
|
|
227
|
+
const text = context.sliceSerialize(token);
|
|
228
|
+
if ((kind === "enter") && !skipHtmlFlowChildren) {
|
|
229
|
+
const previous = current;
|
|
230
|
+
history.push(previous);
|
|
231
|
+
current = {
|
|
232
|
+
type,
|
|
233
|
+
"startLine": startLine + lineDelta,
|
|
234
|
+
startColumn,
|
|
235
|
+
"endLine": endLine + lineDelta,
|
|
236
|
+
endColumn,
|
|
237
|
+
text,
|
|
238
|
+
"children": [],
|
|
239
|
+
"parent": ((previous === root) ? (ancestor || null) : previous)
|
|
240
|
+
};
|
|
241
|
+
if (ancestor) {
|
|
242
|
+
Object.defineProperty(current, htmlFlowSymbol, { "value": true });
|
|
243
|
+
}
|
|
244
|
+
previous.children.push(current);
|
|
245
|
+
flatTokens.push(current);
|
|
246
|
+
if ((current.type === "htmlFlow") && !isHtmlFlowComment(current)) {
|
|
247
|
+
skipHtmlFlowChildren = true;
|
|
248
|
+
if (!reparseOptions || !lines) {
|
|
249
|
+
reparseOptions = {
|
|
250
|
+
...micromarkParseOptions,
|
|
251
|
+
"extensions": [
|
|
252
|
+
{
|
|
253
|
+
"disable": {
|
|
254
|
+
"null": [ "codeIndented", "htmlFlow" ]
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
};
|
|
259
|
+
lines = markdown.split(newLineRe);
|
|
260
|
+
}
|
|
261
|
+
const reparseMarkdown = lines
|
|
262
|
+
.slice(current.startLine - 1, current.endLine)
|
|
263
|
+
.join("\n");
|
|
264
|
+
const tokens = parseInternal(
|
|
265
|
+
reparseMarkdown,
|
|
266
|
+
parseOptions,
|
|
267
|
+
reparseOptions,
|
|
268
|
+
current.startLine - 1,
|
|
269
|
+
current
|
|
270
|
+
);
|
|
271
|
+
current.children = tokens;
|
|
272
|
+
// Avoid stack overflow of Array.push(...spread)
|
|
273
|
+
// eslint-disable-next-line unicorn/prefer-spread
|
|
274
|
+
flatTokens = flatTokens.concat(tokens[flatTokensSymbol]);
|
|
275
|
+
}
|
|
276
|
+
} else if (kind === "exit") {
|
|
277
|
+
if (type === "htmlFlow") {
|
|
278
|
+
skipHtmlFlowChildren = false;
|
|
279
|
+
}
|
|
280
|
+
if (!skipHtmlFlowChildren) {
|
|
281
|
+
if (freezeTokens) {
|
|
282
|
+
Object.freeze(current.children);
|
|
283
|
+
Object.freeze(current);
|
|
284
|
+
}
|
|
285
|
+
// @ts-ignore
|
|
286
|
+
current = history.pop();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Return document
|
|
292
|
+
Object.defineProperty(document, flatTokensSymbol, { "value": flatTokens });
|
|
293
|
+
if (freezeTokens) {
|
|
294
|
+
Object.freeze(document);
|
|
295
|
+
}
|
|
296
|
+
return document;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Parses a Markdown document and returns micromark tokens.
|
|
301
|
+
*
|
|
302
|
+
* @param {string} markdown Markdown document.
|
|
303
|
+
* @param {ParseOptions} [parseOptions] Options.
|
|
304
|
+
* @returns {MicromarkToken[]} Micromark tokens.
|
|
305
|
+
*/
|
|
306
|
+
function parse(markdown, parseOptions) {
|
|
307
|
+
return parseInternal(markdown, parseOptions);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = {
|
|
311
|
+
getEvents,
|
|
312
|
+
parse
|
|
313
|
+
};
|
package/helpers/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markdownlint-rule-helpers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "A collection of markdownlint helper functions for custom rules",
|
|
5
5
|
"main": "./helpers.js",
|
|
6
|
-
"exports":
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./helpers.js",
|
|
8
|
+
"./micromark": "./micromark-helpers.cjs"
|
|
9
|
+
},
|
|
7
10
|
"author": "David Anson (https://dlaa.me/)",
|
|
8
11
|
"license": "MIT",
|
|
9
12
|
"homepage": "https://github.com/DavidAnson/markdownlint",
|
package/helpers/shared.js
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
+
// Symbol for identifing the flat tokens array from micromark parse
|
|
6
|
+
module.exports.flatTokensSymbol = Symbol("flat-tokens");
|
|
7
|
+
|
|
8
|
+
// Symbol for identifying the htmlFlow token from micromark parse
|
|
9
|
+
module.exports.htmlFlowSymbol = Symbol("html-flow");
|
|
10
|
+
|
|
5
11
|
// Regular expression for matching common newline characters
|
|
6
12
|
// See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
|
|
7
13
|
module.exports.newLineRe = /\r\n?|\n/g;
|
package/helpers/test.cjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line n/no-extraneous-require
|
|
6
|
+
const test = require("ava").default;
|
|
7
|
+
const { "exports": packageExports, name } = require("../helpers/package.json");
|
|
8
|
+
|
|
9
|
+
const exportMappings = new Map([
|
|
10
|
+
[ ".", "../helpers/helpers.js" ],
|
|
11
|
+
[ "./micromark", "../helpers/micromark-helpers.cjs" ]
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
test("exportMappings", (t) => {
|
|
15
|
+
t.deepEqual(
|
|
16
|
+
Object.keys(packageExports),
|
|
17
|
+
[ ...exportMappings.keys() ]
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
for (const [ exportName, exportPath ] of exportMappings) {
|
|
22
|
+
test(exportName, (t) => {
|
|
23
|
+
t.is(
|
|
24
|
+
require(exportName.replace(/^\./u, name)),
|
|
25
|
+
require(exportPath)
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
}
|
package/lib/cache.js
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
5
|
const helpers = require("../helpers");
|
|
6
|
-
const { filterByTypes } = require("../helpers/micromark.cjs");
|
|
6
|
+
const { filterByTypes } = require("../helpers/micromark-helpers.cjs");
|
|
7
7
|
|
|
8
8
|
/** @type {Map<string, object>} */
|
|
9
9
|
const map = new Map();
|
|
10
|
-
// eslint-disable-next-line no-undef-init
|
|
11
10
|
let params = undefined;
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -46,7 +45,8 @@ function getCached(name, getValue) {
|
|
|
46
45
|
*/
|
|
47
46
|
function filterByTypesCached(types, htmlFlow) {
|
|
48
47
|
return getCached(
|
|
49
|
-
|
|
48
|
+
// eslint-disable-next-line prefer-rest-params
|
|
49
|
+
JSON.stringify(arguments),
|
|
50
50
|
() => filterByTypes(params.parsers.micromark.tokens, types, htmlFlow)
|
|
51
51
|
);
|
|
52
52
|
}
|