markdownlint 0.24.0 → 0.26.0
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/CONTRIBUTING.md +15 -3
- package/LICENSE +1 -1
- package/README.md +138 -87
- package/demo/markdownlint-browser.js +2028 -1364
- package/doc/CustomRules.md +15 -4
- package/doc/Rules.md +217 -26
- package/helpers/LICENSE +1 -1
- package/helpers/helpers.js +541 -188
- package/helpers/package.json +4 -1
- package/lib/cache.js +15 -25
- package/lib/constants.js +7 -0
- package/lib/markdownlint.d.ts +27 -19
- package/lib/markdownlint.js +438 -344
- package/lib/md004.js +4 -4
- package/lib/md005.js +4 -4
- package/lib/md006.js +4 -4
- package/lib/md007.js +8 -6
- package/lib/md009.js +20 -13
- package/lib/md010.js +34 -16
- package/lib/md011.js +4 -4
- package/lib/md013.js +4 -5
- package/lib/md014.js +4 -4
- package/lib/md021.js +3 -1
- package/lib/md026.js +1 -1
- package/lib/md027.js +2 -2
- package/lib/md028.js +2 -2
- package/lib/md029.js +5 -4
- package/lib/md030.js +4 -4
- package/lib/md031.js +2 -2
- package/lib/md032.js +3 -2
- package/lib/md033.js +12 -9
- package/lib/md034.js +2 -2
- package/lib/md035.js +10 -5
- package/lib/md036.js +2 -2
- package/lib/md037.js +5 -3
- package/lib/md038.js +7 -4
- package/lib/md039.js +8 -5
- package/lib/md042.js +17 -10
- package/lib/md043.js +2 -3
- package/lib/md044.js +21 -10
- package/lib/md046.js +14 -13
- package/lib/md048.js +13 -14
- package/lib/md049-md050.js +73 -0
- package/lib/md051.js +84 -0
- package/lib/md052.js +35 -0
- package/lib/md053.js +52 -0
- package/lib/rules.js +8 -6
- package/package.json +37 -32
- package/schema/.markdownlint.jsonc +30 -3
- package/schema/.markdownlint.yaml +26 -1
- package/schema/markdownlint-config-schema.json +159 -683
package/helpers/helpers.js
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const os = require("os");
|
|
6
|
-
|
|
7
5
|
// Regular expression for matching common newline characters
|
|
8
6
|
// See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
|
|
9
7
|
const newLineRe = /\r\n?|\n/g;
|
|
@@ -14,11 +12,15 @@ module.exports.frontMatterRe =
|
|
|
14
12
|
// eslint-disable-next-line max-len
|
|
15
13
|
/((^---\s*$[^]*?^---\s*$)|(^\+\+\+\s*$[^]*?^(\+\+\+|\.\.\.)\s*$)|(^\{\s*$[^]*?^\}\s*$))(\r\n|\r|\n|$)/m;
|
|
16
14
|
|
|
17
|
-
// Regular expression for matching inline disable/enable comments
|
|
18
|
-
const
|
|
15
|
+
// Regular expression for matching the start of inline disable/enable comments
|
|
16
|
+
const inlineCommentStartRe =
|
|
19
17
|
// eslint-disable-next-line max-len
|
|
20
|
-
|
|
21
|
-
module.exports.
|
|
18
|
+
/(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-line|disable-next-line|configure-file))(?:\s|-->)/ig;
|
|
19
|
+
module.exports.inlineCommentStartRe = inlineCommentStartRe;
|
|
20
|
+
|
|
21
|
+
// Regular expression for matching HTML elements
|
|
22
|
+
const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^`>]*)?)\/?>/g;
|
|
23
|
+
module.exports.htmlElementRe = htmlElementRe;
|
|
22
24
|
|
|
23
25
|
// Regular expressions for range matching
|
|
24
26
|
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*(?:\/|[^\s\]"'\W])/ig;
|
|
@@ -28,12 +30,13 @@ module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
|
|
|
28
30
|
// Regular expression for all instances of emphasis markers
|
|
29
31
|
const emphasisMarkersRe = /[_*]/g;
|
|
30
32
|
|
|
31
|
-
// Regular expression for
|
|
32
|
-
const
|
|
33
|
-
|
|
33
|
+
// Regular expression for reference links (full and collapsed but not shortcut)
|
|
34
|
+
const referenceLinkRe =
|
|
35
|
+
/!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|[^(]|$)/g;
|
|
34
36
|
|
|
35
|
-
// Regular expression for link reference
|
|
36
|
-
|
|
37
|
+
// Regular expression for link reference definitions
|
|
38
|
+
const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/;
|
|
39
|
+
module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe;
|
|
37
40
|
|
|
38
41
|
// All punctuation characters (normal and full-width)
|
|
39
42
|
const allPunctuation = ".,;:!?。,;:!?";
|
|
@@ -62,14 +65,43 @@ module.exports.isObject = function isObject(obj) {
|
|
|
62
65
|
return (obj !== null) && (typeof obj === "object") && !Array.isArray(obj);
|
|
63
66
|
};
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Returns true iff the input line is blank (contains nothing, whitespace, or
|
|
70
|
+
* comments (unclosed start/end comments allowed)).
|
|
71
|
+
*
|
|
72
|
+
* @param {string} line Input line.
|
|
73
|
+
* @returns {boolean} True iff line is blank.
|
|
74
|
+
*/
|
|
75
|
+
function isBlankLine(line) {
|
|
76
|
+
const startComment = "<!--";
|
|
77
|
+
const endComment = "-->";
|
|
78
|
+
const removeComments = (s) => {
|
|
79
|
+
// eslint-disable-next-line no-constant-condition
|
|
80
|
+
while (true) {
|
|
81
|
+
const start = s.indexOf(startComment);
|
|
82
|
+
const end = s.indexOf(endComment);
|
|
83
|
+
if ((end !== -1) && ((start === -1) || (end < start))) {
|
|
84
|
+
// Unmatched end comment is first
|
|
85
|
+
s = s.slice(end + endComment.length);
|
|
86
|
+
} else if ((start !== -1) && (end !== -1)) {
|
|
87
|
+
// Start comment is before end comment
|
|
88
|
+
s = s.slice(0, start) + s.slice(end + endComment.length);
|
|
89
|
+
} else if ((start !== -1) && (end === -1)) {
|
|
90
|
+
// Unmatched start comment is last
|
|
91
|
+
s = s.slice(0, start);
|
|
92
|
+
} else {
|
|
93
|
+
// No more comments to remove
|
|
94
|
+
return s;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
return (
|
|
99
|
+
!line ||
|
|
100
|
+
!line.trim() ||
|
|
101
|
+
!removeComments(line).replace(/>/g, "").trim()
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
module.exports.isBlankLine = isBlankLine;
|
|
73
105
|
|
|
74
106
|
/**
|
|
75
107
|
* Compare function for Array.prototype.sort for ascending order of numbers.
|
|
@@ -129,16 +161,10 @@ module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
|
|
|
129
161
|
!content.endsWith("-") && !content.includes("--"));
|
|
130
162
|
// If a valid block/inline comment...
|
|
131
163
|
if (isValid) {
|
|
132
|
-
|
|
133
|
-
.slice(
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
if (inlineCommentIndex === -1) {
|
|
137
|
-
text =
|
|
138
|
-
text.slice(0, i + htmlCommentBegin.length) +
|
|
139
|
-
content.replace(/[^\r\n]/g, ".") +
|
|
140
|
-
text.slice(j);
|
|
141
|
-
}
|
|
164
|
+
text =
|
|
165
|
+
text.slice(0, i + htmlCommentBegin.length) +
|
|
166
|
+
content.replace(/[^\r\n]/g, ".") +
|
|
167
|
+
text.slice(j);
|
|
142
168
|
}
|
|
143
169
|
}
|
|
144
170
|
}
|
|
@@ -181,6 +207,22 @@ module.exports.fencedCodeBlockStyleFor =
|
|
|
181
207
|
}
|
|
182
208
|
};
|
|
183
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Return the string representation of a emphasis or strong markup character.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} markup Emphasis or strong string.
|
|
214
|
+
* @returns {string} String representation.
|
|
215
|
+
*/
|
|
216
|
+
module.exports.emphasisOrStrongStyleFor =
|
|
217
|
+
function emphasisOrStrongStyleFor(markup) {
|
|
218
|
+
switch (markup[0]) {
|
|
219
|
+
case "*":
|
|
220
|
+
return "asterisk";
|
|
221
|
+
default:
|
|
222
|
+
return "underscore";
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
184
226
|
/**
|
|
185
227
|
* Return the number of characters of indent for a token.
|
|
186
228
|
*
|
|
@@ -231,11 +273,11 @@ module.exports.unorderedListStyleFor = function unorderedListStyleFor(token) {
|
|
|
231
273
|
* @returns {void}
|
|
232
274
|
*/
|
|
233
275
|
function filterTokens(params, type, handler) {
|
|
234
|
-
params.tokens
|
|
276
|
+
for (const token of params.tokens) {
|
|
235
277
|
if (token.type === type) {
|
|
236
278
|
handler(token);
|
|
237
279
|
}
|
|
238
|
-
}
|
|
280
|
+
}
|
|
239
281
|
}
|
|
240
282
|
module.exports.filterTokens = filterTokens;
|
|
241
283
|
|
|
@@ -247,11 +289,12 @@ module.exports.filterTokens = filterTokens;
|
|
|
247
289
|
*/
|
|
248
290
|
function isMathBlock(token) {
|
|
249
291
|
return (
|
|
250
|
-
(token.tag === "math") &&
|
|
292
|
+
((token.tag === "$$") || (token.tag === "math")) &&
|
|
251
293
|
token.type.startsWith("math_block") &&
|
|
252
294
|
!token.type.endsWith("_end")
|
|
253
295
|
);
|
|
254
296
|
}
|
|
297
|
+
module.exports.isMathBlock = isMathBlock;
|
|
255
298
|
|
|
256
299
|
// Get line metadata array
|
|
257
300
|
module.exports.getLineMetadata = function getLineMetadata(params) {
|
|
@@ -285,22 +328,28 @@ module.exports.getLineMetadata = function getLineMetadata(params) {
|
|
|
285
328
|
filterTokens(params, "hr", (token) => {
|
|
286
329
|
lineMetadata[token.map[0]][6] = true;
|
|
287
330
|
});
|
|
288
|
-
params.tokens.filter(isMathBlock)
|
|
331
|
+
for (const token of params.tokens.filter(isMathBlock)) {
|
|
289
332
|
for (let i = token.map[0]; i < token.map[1]; i++) {
|
|
290
333
|
lineMetadata[i][7] = true;
|
|
291
334
|
}
|
|
292
|
-
}
|
|
335
|
+
}
|
|
293
336
|
return lineMetadata;
|
|
294
337
|
};
|
|
295
338
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Calls the provided function for each line.
|
|
341
|
+
*
|
|
342
|
+
* @param {Object} lineMetadata Line metadata object.
|
|
343
|
+
* @param {Function} handler Function taking (line, lineIndex, inCode, onFence,
|
|
344
|
+
* inTable, inItem, inBreak, inMath).
|
|
345
|
+
* @returns {void}
|
|
346
|
+
*/
|
|
347
|
+
function forEachLine(lineMetadata, handler) {
|
|
348
|
+
for (const metadata of lineMetadata) {
|
|
301
349
|
handler(...metadata);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
module.exports.forEachLine = forEachLine;
|
|
304
353
|
|
|
305
354
|
// Returns (nested) lists as a flat array (in order)
|
|
306
355
|
module.exports.flattenLists = function flattenLists(tokens) {
|
|
@@ -310,11 +359,7 @@ module.exports.flattenLists = function flattenLists(tokens) {
|
|
|
310
359
|
let nesting = 0;
|
|
311
360
|
const nestingStack = [];
|
|
312
361
|
let lastWithMap = { "map": [ 0, 1 ] };
|
|
313
|
-
|
|
314
|
-
if (isMathBlock(token) && token.map[1]) {
|
|
315
|
-
// markdown-it-texmath plugin does not account for math_block_end
|
|
316
|
-
token.map[1]++;
|
|
317
|
-
}
|
|
362
|
+
for (const token of tokens) {
|
|
318
363
|
if ((token.type === "bullet_list_open") ||
|
|
319
364
|
(token.type === "ordered_list_open")) {
|
|
320
365
|
// Save current context and start a new one
|
|
@@ -347,12 +392,13 @@ module.exports.flattenLists = function flattenLists(tokens) {
|
|
|
347
392
|
nestingStack.push(nesting);
|
|
348
393
|
nesting = 0;
|
|
349
394
|
} else if (token.type === "blockquote_close") {
|
|
350
|
-
nesting = nestingStack.pop();
|
|
351
|
-
}
|
|
395
|
+
nesting = nestingStack.pop() || 0;
|
|
396
|
+
}
|
|
397
|
+
if (token.map) {
|
|
352
398
|
// Track last token with map
|
|
353
399
|
lastWithMap = token;
|
|
354
400
|
}
|
|
355
|
-
}
|
|
401
|
+
}
|
|
356
402
|
return flattenedLists;
|
|
357
403
|
};
|
|
358
404
|
|
|
@@ -360,114 +406,102 @@ module.exports.flattenLists = function flattenLists(tokens) {
|
|
|
360
406
|
module.exports.forEachInlineChild =
|
|
361
407
|
function forEachInlineChild(params, type, handler) {
|
|
362
408
|
filterTokens(params, "inline", function forToken(token) {
|
|
363
|
-
token.children
|
|
409
|
+
for (const child of token.children) {
|
|
364
410
|
if (child.type === type) {
|
|
365
411
|
handler(child, token);
|
|
366
412
|
}
|
|
367
|
-
}
|
|
413
|
+
}
|
|
368
414
|
});
|
|
369
415
|
};
|
|
370
416
|
|
|
371
417
|
// Calls the provided function for each heading's content
|
|
372
418
|
module.exports.forEachHeading = function forEachHeading(params, handler) {
|
|
373
419
|
let heading = null;
|
|
374
|
-
params.tokens
|
|
420
|
+
for (const token of params.tokens) {
|
|
375
421
|
if (token.type === "heading_open") {
|
|
376
422
|
heading = token;
|
|
377
423
|
} else if (token.type === "heading_close") {
|
|
378
424
|
heading = null;
|
|
379
425
|
} else if ((token.type === "inline") && heading) {
|
|
380
|
-
handler(heading, token.content);
|
|
426
|
+
handler(heading, token.content, token);
|
|
381
427
|
}
|
|
382
|
-
}
|
|
428
|
+
}
|
|
383
429
|
};
|
|
384
430
|
|
|
385
431
|
/**
|
|
386
432
|
* Calls the provided function for each inline code span's content.
|
|
387
433
|
*
|
|
388
434
|
* @param {string} input Markdown content.
|
|
389
|
-
* @param {Function} handler Callback function
|
|
435
|
+
* @param {Function} handler Callback function taking (code, lineIndex,
|
|
436
|
+
* columnIndex, ticks).
|
|
390
437
|
* @returns {void}
|
|
391
438
|
*/
|
|
392
439
|
function forEachInlineCodeSpan(input, handler) {
|
|
393
|
-
|
|
394
|
-
let
|
|
395
|
-
|
|
396
|
-
while (
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if ((char === "`") && (state !== "linkDestinationOpen")) {
|
|
422
|
-
// Count backticks at start or end of code span
|
|
423
|
-
currentTicks++;
|
|
424
|
-
if ((startIndex === -1) || (startColumn === -1)) {
|
|
425
|
-
startIndex = index + 1;
|
|
426
|
-
}
|
|
427
|
-
} else {
|
|
428
|
-
if ((startIndex >= 0) &&
|
|
429
|
-
(startColumn >= 0) &&
|
|
430
|
-
(tickCount === currentTicks)) {
|
|
431
|
-
// Found end backticks; invoke callback for code span
|
|
440
|
+
const backtickRe = /`+/g;
|
|
441
|
+
let match = null;
|
|
442
|
+
const backticksLengthAndIndex = [];
|
|
443
|
+
while ((match = backtickRe.exec(input)) !== null) {
|
|
444
|
+
backticksLengthAndIndex.push([ match[0].length, match.index ]);
|
|
445
|
+
}
|
|
446
|
+
const newLinesIndex = [];
|
|
447
|
+
while ((match = newLineRe.exec(input)) !== null) {
|
|
448
|
+
newLinesIndex.push(match.index);
|
|
449
|
+
}
|
|
450
|
+
let lineIndex = 0;
|
|
451
|
+
let lineStartIndex = 0;
|
|
452
|
+
let k = 0;
|
|
453
|
+
for (let i = 0; i < backticksLengthAndIndex.length - 1; i++) {
|
|
454
|
+
const [ startLength, startIndex ] = backticksLengthAndIndex[i];
|
|
455
|
+
if ((startIndex === 0) || (input[startIndex - 1] !== "\\")) {
|
|
456
|
+
for (let j = i + 1; j < backticksLengthAndIndex.length; j++) {
|
|
457
|
+
const [ endLength, endIndex ] = backticksLengthAndIndex[j];
|
|
458
|
+
if (startLength === endLength) {
|
|
459
|
+
for (; k < newLinesIndex.length; k++) {
|
|
460
|
+
const newLineIndex = newLinesIndex[k];
|
|
461
|
+
if (startIndex < newLineIndex) {
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
lineIndex++;
|
|
465
|
+
lineStartIndex = newLineIndex + 1;
|
|
466
|
+
}
|
|
467
|
+
const columnIndex = startIndex - lineStartIndex + startLength;
|
|
432
468
|
handler(
|
|
433
|
-
input.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
startLine = currentLine;
|
|
441
|
-
startColumn = currentColumn;
|
|
469
|
+
input.slice(startIndex + startLength, endIndex),
|
|
470
|
+
lineIndex,
|
|
471
|
+
columnIndex,
|
|
472
|
+
startLength
|
|
473
|
+
);
|
|
474
|
+
i = j;
|
|
475
|
+
break;
|
|
442
476
|
}
|
|
443
|
-
// Not in backticks
|
|
444
|
-
currentTicks = 0;
|
|
445
|
-
}
|
|
446
|
-
if (char === "\n") {
|
|
447
|
-
// On next line
|
|
448
|
-
currentLine++;
|
|
449
|
-
currentColumn = 0;
|
|
450
|
-
} else if ((char === "\\") &&
|
|
451
|
-
((startIndex === -1) || (startColumn === -1)) &&
|
|
452
|
-
(input[index + 1] !== "\n")) {
|
|
453
|
-
// Escape character outside code, skip next
|
|
454
|
-
index++;
|
|
455
|
-
currentColumn += 2;
|
|
456
|
-
} else {
|
|
457
|
-
// On next column
|
|
458
|
-
currentColumn++;
|
|
459
477
|
}
|
|
460
478
|
}
|
|
461
|
-
if (startIndex >= 0) {
|
|
462
|
-
// Restart loop after unmatched start backticks (ex: "`text``code``")
|
|
463
|
-
index = startIndex;
|
|
464
|
-
currentLine = startLine;
|
|
465
|
-
currentColumn = startColumn;
|
|
466
|
-
}
|
|
467
479
|
}
|
|
468
480
|
}
|
|
469
481
|
module.exports.forEachInlineCodeSpan = forEachInlineCodeSpan;
|
|
470
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Adds ellipsis to the left/right/middle of the specified text.
|
|
485
|
+
*
|
|
486
|
+
* @param {string} text Text to ellipsify.
|
|
487
|
+
* @param {boolean} [start] True iff the start of the text is important.
|
|
488
|
+
* @param {boolean} [end] True iff the end of the text is important.
|
|
489
|
+
* @returns {string} Ellipsified text.
|
|
490
|
+
*/
|
|
491
|
+
function ellipsify(text, start, end) {
|
|
492
|
+
if (text.length <= 30) {
|
|
493
|
+
// Nothing to do
|
|
494
|
+
} else if (start && end) {
|
|
495
|
+
text = text.slice(0, 15) + "..." + text.slice(-15);
|
|
496
|
+
} else if (end) {
|
|
497
|
+
text = "..." + text.slice(-30);
|
|
498
|
+
} else {
|
|
499
|
+
text = text.slice(0, 30) + "...";
|
|
500
|
+
}
|
|
501
|
+
return text;
|
|
502
|
+
}
|
|
503
|
+
module.exports.ellipsify = ellipsify;
|
|
504
|
+
|
|
471
505
|
/**
|
|
472
506
|
* Adds a generic error object via the onError callback.
|
|
473
507
|
*
|
|
@@ -508,58 +542,83 @@ module.exports.addErrorDetailIf = function addErrorDetailIf(
|
|
|
508
542
|
// Adds an error object with context via the onError callback
|
|
509
543
|
module.exports.addErrorContext = function addErrorContext(
|
|
510
544
|
onError, lineNumber, context, left, right, range, fixInfo) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
} else if (left && right) {
|
|
514
|
-
context = context.substr(0, 15) + "..." + context.substr(-15);
|
|
515
|
-
} else if (right) {
|
|
516
|
-
context = "..." + context.substr(-30);
|
|
517
|
-
} else {
|
|
518
|
-
context = context.substr(0, 30) + "...";
|
|
519
|
-
}
|
|
520
|
-
addError(onError, lineNumber, null, context, range, fixInfo);
|
|
545
|
+
context = ellipsify(context, left, right);
|
|
546
|
+
addError(onError, lineNumber, undefined, context, range, fixInfo);
|
|
521
547
|
};
|
|
522
548
|
|
|
523
549
|
/**
|
|
524
|
-
* Returns an array of code span ranges.
|
|
550
|
+
* Returns an array of code block and span content ranges.
|
|
525
551
|
*
|
|
526
|
-
* @param {
|
|
527
|
-
* @
|
|
552
|
+
* @param {Object} params RuleParams instance.
|
|
553
|
+
* @param {Object} lineMetadata Line metadata object.
|
|
554
|
+
* @returns {number[][]} Array of ranges (lineIndex, columnIndex, length).
|
|
528
555
|
*/
|
|
529
|
-
module.exports.
|
|
556
|
+
module.exports.codeBlockAndSpanRanges = (params, lineMetadata) => {
|
|
530
557
|
const exclusions = [];
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
(
|
|
534
|
-
|
|
535
|
-
// eslint-disable-next-line unicorn/no-for-loop
|
|
536
|
-
for (let i = 0; i < codeLines.length; i++) {
|
|
537
|
-
exclusions.push(
|
|
538
|
-
[ lineIndex + i, columnIndex, codeLines[i].length ]
|
|
539
|
-
);
|
|
540
|
-
columnIndex = 0;
|
|
541
|
-
}
|
|
558
|
+
// Add code block ranges (excludes fences)
|
|
559
|
+
forEachLine(lineMetadata, (line, lineIndex, inCode, onFence) => {
|
|
560
|
+
if (inCode && !onFence) {
|
|
561
|
+
exclusions.push([ lineIndex, 0, line.length ]);
|
|
542
562
|
}
|
|
543
|
-
);
|
|
563
|
+
});
|
|
564
|
+
// Add code span ranges (excludes ticks)
|
|
565
|
+
filterTokens(params, "inline", (token) => {
|
|
566
|
+
if (token.children.some((child) => child.type === "code_inline")) {
|
|
567
|
+
const tokenLines = params.lines.slice(token.map[0], token.map[1]);
|
|
568
|
+
forEachInlineCodeSpan(
|
|
569
|
+
tokenLines.join("\n"),
|
|
570
|
+
(code, lineIndex, columnIndex) => {
|
|
571
|
+
const codeLines = code.split(newLineRe);
|
|
572
|
+
for (const [ i, line ] of codeLines.entries()) {
|
|
573
|
+
exclusions.push([
|
|
574
|
+
token.lineNumber - 1 + lineIndex + i,
|
|
575
|
+
i ? 0 : columnIndex,
|
|
576
|
+
line.length
|
|
577
|
+
]);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
return exclusions;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Returns an array of HTML element ranges.
|
|
588
|
+
*
|
|
589
|
+
* @param {Object} params RuleParams instance.
|
|
590
|
+
* @param {Object} lineMetadata Line metadata object.
|
|
591
|
+
* @returns {number[][]} Array of ranges (lineIndex, columnIndex, length).
|
|
592
|
+
*/
|
|
593
|
+
module.exports.htmlElementRanges = (params, lineMetadata) => {
|
|
594
|
+
const exclusions = [];
|
|
595
|
+
forEachLine(lineMetadata, (line, lineIndex, inCode) => {
|
|
596
|
+
let match = null;
|
|
597
|
+
// eslint-disable-next-line no-unmodified-loop-condition
|
|
598
|
+
while (!inCode && ((match = htmlElementRe.exec(line)) !== null)) {
|
|
599
|
+
exclusions.push([ lineIndex, match.index, match[0].length ]);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
544
602
|
return exclusions;
|
|
545
603
|
};
|
|
546
604
|
|
|
547
605
|
/**
|
|
548
|
-
* Determines whether the specified range
|
|
606
|
+
* Determines whether the specified range is within another range.
|
|
549
607
|
*
|
|
550
608
|
* @param {number[][]} ranges Array of ranges (line, index, length).
|
|
551
609
|
* @param {number} lineIndex Line index to check.
|
|
552
610
|
* @param {number} index Index to check.
|
|
553
611
|
* @param {number} length Length to check.
|
|
554
|
-
* @returns {boolean} True iff the specified range
|
|
612
|
+
* @returns {boolean} True iff the specified range is within.
|
|
555
613
|
*/
|
|
556
|
-
|
|
614
|
+
const withinAnyRange = (ranges, lineIndex, index, length) => (
|
|
557
615
|
!ranges.every((span) => (
|
|
558
616
|
(lineIndex !== span[0]) ||
|
|
559
|
-
(index
|
|
560
|
-
(index > span[1] + span[2])
|
|
617
|
+
(index < span[1]) ||
|
|
618
|
+
(index + length > span[1] + span[2])
|
|
561
619
|
))
|
|
562
620
|
);
|
|
621
|
+
module.exports.withinAnyRange = withinAnyRange;
|
|
563
622
|
|
|
564
623
|
// Returns a range object for a line by applying a RegExp
|
|
565
624
|
module.exports.rangeFromRegExp = function rangeFromRegExp(line, regexp) {
|
|
@@ -587,6 +646,82 @@ module.exports.frontMatterHasTitle =
|
|
|
587
646
|
frontMatterLines.some((line) => frontMatterTitleRe.test(line));
|
|
588
647
|
};
|
|
589
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Calls the provided function for each link.
|
|
651
|
+
*
|
|
652
|
+
* @param {string} line Line of Markdown input.
|
|
653
|
+
* @param {Function} handler Function taking (index, link, text, destination).
|
|
654
|
+
* @returns {void}
|
|
655
|
+
*/
|
|
656
|
+
function forEachLink(line, handler) {
|
|
657
|
+
// Helper to find matching close symbol for link text/destination
|
|
658
|
+
const findClosingSymbol = (index) => {
|
|
659
|
+
const begin = line[index];
|
|
660
|
+
const end = (begin === "[") ? "]" : ")";
|
|
661
|
+
let nesting = 0;
|
|
662
|
+
let escaping = false;
|
|
663
|
+
let pointy = false;
|
|
664
|
+
for (let i = index + 1; i < line.length; i++) {
|
|
665
|
+
const current = line[i];
|
|
666
|
+
if (current === "\\") {
|
|
667
|
+
escaping = !escaping;
|
|
668
|
+
} else if (!escaping && (current === begin)) {
|
|
669
|
+
nesting++;
|
|
670
|
+
} else if (!escaping && (current === end)) {
|
|
671
|
+
if (nesting > 0) {
|
|
672
|
+
nesting--;
|
|
673
|
+
} else if (!pointy) {
|
|
674
|
+
// Return index after matching close symbol
|
|
675
|
+
return i + 1;
|
|
676
|
+
}
|
|
677
|
+
} else if ((i === index + 1) && (begin === "(") && (current === "<")) {
|
|
678
|
+
pointy = true;
|
|
679
|
+
} else if (!escaping && pointy && current === ">") {
|
|
680
|
+
pointy = false;
|
|
681
|
+
nesting = 0;
|
|
682
|
+
} else {
|
|
683
|
+
escaping = false;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// No match found
|
|
687
|
+
return -1;
|
|
688
|
+
};
|
|
689
|
+
// Scan line for unescaped "[" character
|
|
690
|
+
let escaping = false;
|
|
691
|
+
for (let i = 0; i < line.length; i++) {
|
|
692
|
+
const current = line[i];
|
|
693
|
+
if (current === "\\") {
|
|
694
|
+
escaping = !escaping;
|
|
695
|
+
} else if (!escaping && (current === "[")) {
|
|
696
|
+
// Scan for matching close "]" of link text
|
|
697
|
+
const textEnd = findClosingSymbol(i);
|
|
698
|
+
if (textEnd !== -1) {
|
|
699
|
+
if ((line[textEnd] === "(") || (line[textEnd] === "[")) {
|
|
700
|
+
// Scan for matching close ")" or "]" of link destination
|
|
701
|
+
const destEnd = findClosingSymbol(textEnd);
|
|
702
|
+
if (destEnd !== -1) {
|
|
703
|
+
// Call handler with link text and destination
|
|
704
|
+
const link = line.slice(i, destEnd);
|
|
705
|
+
const text = line.slice(i, textEnd);
|
|
706
|
+
const dest = line.slice(textEnd, destEnd);
|
|
707
|
+
handler(i, link, text, dest);
|
|
708
|
+
i = destEnd;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (i < textEnd) {
|
|
712
|
+
// Call handler with link text only
|
|
713
|
+
const text = line.slice(i, textEnd);
|
|
714
|
+
handler(i, text, text);
|
|
715
|
+
i = textEnd;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
escaping = false;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
module.exports.forEachLink = forEachLink;
|
|
724
|
+
|
|
590
725
|
/**
|
|
591
726
|
* Returns a list of emphasis markers in code spans and links.
|
|
592
727
|
*
|
|
@@ -596,6 +731,17 @@ module.exports.frontMatterHasTitle =
|
|
|
596
731
|
function emphasisMarkersInContent(params) {
|
|
597
732
|
const { lines } = params;
|
|
598
733
|
const byLine = new Array(lines.length);
|
|
734
|
+
// Search links
|
|
735
|
+
for (const [ tokenLineIndex, tokenLine ] of lines.entries()) {
|
|
736
|
+
const inLine = [];
|
|
737
|
+
forEachLink(tokenLine, (index, match) => {
|
|
738
|
+
let markerMatch = null;
|
|
739
|
+
while ((markerMatch = emphasisMarkersRe.exec(match))) {
|
|
740
|
+
inLine.push(index + markerMatch.index);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
byLine[tokenLineIndex] = inLine;
|
|
744
|
+
}
|
|
599
745
|
// Search code spans
|
|
600
746
|
filterTokens(params, "inline", (token) => {
|
|
601
747
|
const { children, lineNumber, map } = token;
|
|
@@ -605,48 +751,172 @@ function emphasisMarkersInContent(params) {
|
|
|
605
751
|
tokenLines.join("\n"),
|
|
606
752
|
(code, lineIndex, column, tickCount) => {
|
|
607
753
|
const codeLines = code.split(newLineRe);
|
|
608
|
-
codeLines.
|
|
754
|
+
for (const [ codeLineIndex, codeLine ] of codeLines.entries()) {
|
|
755
|
+
const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex;
|
|
756
|
+
const inLine = byLine[byLineIndex];
|
|
757
|
+
const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount;
|
|
609
758
|
let match = null;
|
|
610
759
|
while ((match = emphasisMarkersRe.exec(codeLine))) {
|
|
611
|
-
const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex;
|
|
612
|
-
const inLine = byLine[byLineIndex] || [];
|
|
613
|
-
const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount;
|
|
614
760
|
inLine.push(codeLineOffset + match.index);
|
|
615
|
-
byLine[byLineIndex] = inLine;
|
|
616
761
|
}
|
|
617
|
-
|
|
762
|
+
byLine[byLineIndex] = inLine;
|
|
763
|
+
}
|
|
618
764
|
}
|
|
619
765
|
);
|
|
620
766
|
}
|
|
621
767
|
});
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
768
|
+
return byLine;
|
|
769
|
+
}
|
|
770
|
+
module.exports.emphasisMarkersInContent = emphasisMarkersInContent;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Returns an object with information about reference links and images.
|
|
774
|
+
*
|
|
775
|
+
* @param {Object} lineMetadata Line metadata object.
|
|
776
|
+
* @returns {Object} Reference link/image data.
|
|
777
|
+
*/
|
|
778
|
+
function getReferenceLinkImageData(lineMetadata) {
|
|
779
|
+
// Initialize return values
|
|
780
|
+
const references = new Map();
|
|
781
|
+
const shortcuts = new Set();
|
|
782
|
+
const definitions = new Map();
|
|
783
|
+
const duplicateDefinitions = [];
|
|
784
|
+
// Define helper functions
|
|
785
|
+
const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
|
|
786
|
+
const exclusions = [];
|
|
787
|
+
const excluded = (match) => withinAnyRange(
|
|
788
|
+
exclusions, 0, match.index, match[0].length
|
|
789
|
+
);
|
|
790
|
+
// Convert input to single-line so multi-line links/images are easier
|
|
791
|
+
const lineOffsets = [];
|
|
792
|
+
let currentOffset = 0;
|
|
793
|
+
const contentLines = [];
|
|
794
|
+
forEachLine(lineMetadata, (line, lineIndex, inCode) => {
|
|
795
|
+
lineOffsets[lineIndex] = currentOffset;
|
|
796
|
+
if (!inCode) {
|
|
797
|
+
if (line.trim().length === 0) {
|
|
798
|
+
// Allow RegExp to detect the end of a block
|
|
799
|
+
line = "\0";
|
|
631
800
|
}
|
|
801
|
+
contentLines.push(line);
|
|
802
|
+
currentOffset += line.length + 1;
|
|
632
803
|
}
|
|
633
804
|
});
|
|
634
|
-
|
|
805
|
+
lineOffsets.push(currentOffset);
|
|
806
|
+
const contentLine = contentLines.join(" ");
|
|
807
|
+
// Determine single-line exclusions for inline code spans
|
|
808
|
+
forEachInlineCodeSpan(contentLine, (code, lineIndex, columnIndex) => {
|
|
809
|
+
exclusions.push([ 0, columnIndex, code.length ]);
|
|
810
|
+
});
|
|
811
|
+
// Identify all link/image reference definitions
|
|
812
|
+
forEachLine(lineMetadata, (line, lineIndex, inCode) => {
|
|
813
|
+
if (!inCode) {
|
|
814
|
+
const linkReferenceDefinitionMatch = linkReferenceDefinitionRe.exec(line);
|
|
815
|
+
if (linkReferenceDefinitionMatch) {
|
|
816
|
+
const label = normalizeLabel(linkReferenceDefinitionMatch[1]);
|
|
817
|
+
if (definitions.has(label)) {
|
|
818
|
+
duplicateDefinitions.push([ label, lineIndex ]);
|
|
819
|
+
} else {
|
|
820
|
+
definitions.set(label, lineIndex);
|
|
821
|
+
}
|
|
822
|
+
exclusions.push([ 0, lineOffsets[lineIndex], line.length ]);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
// Identify all link and image references
|
|
827
|
+
let lineIndex = 0;
|
|
828
|
+
const pendingContents = [
|
|
829
|
+
{
|
|
830
|
+
"content": contentLine,
|
|
831
|
+
"contentLineIndex": 0,
|
|
832
|
+
"contentIndex": 0,
|
|
833
|
+
"topLevel": true
|
|
834
|
+
}
|
|
835
|
+
];
|
|
836
|
+
let pendingContent = null;
|
|
837
|
+
while ((pendingContent = pendingContents.shift())) {
|
|
838
|
+
const { content, contentLineIndex, contentIndex, topLevel } =
|
|
839
|
+
pendingContent;
|
|
840
|
+
let referenceLinkMatch = null;
|
|
841
|
+
while ((referenceLinkMatch = referenceLinkRe.exec(content)) !== null) {
|
|
842
|
+
const [ matchString, matchText, matchLabel ] = referenceLinkMatch;
|
|
843
|
+
if (
|
|
844
|
+
!matchString.startsWith("\\") &&
|
|
845
|
+
!matchString.startsWith("!\\") &&
|
|
846
|
+
!matchText.endsWith("\\") &&
|
|
847
|
+
!(matchLabel || "").endsWith("\\") &&
|
|
848
|
+
(topLevel || matchString.startsWith("!")) &&
|
|
849
|
+
!excluded(referenceLinkMatch)
|
|
850
|
+
) {
|
|
851
|
+
const shortcutLink = (matchLabel === undefined);
|
|
852
|
+
const collapsedLink =
|
|
853
|
+
(!shortcutLink && (matchLabel.length === 0));
|
|
854
|
+
const label = normalizeLabel(
|
|
855
|
+
(shortcutLink || collapsedLink) ? matchText : matchLabel
|
|
856
|
+
);
|
|
857
|
+
if (label.length > 0) {
|
|
858
|
+
if (shortcutLink) {
|
|
859
|
+
// Track, but don't validate due to ambiguity: "text [text] text"
|
|
860
|
+
shortcuts.add(label);
|
|
861
|
+
} else {
|
|
862
|
+
const referenceindex = referenceLinkMatch.index;
|
|
863
|
+
if (topLevel) {
|
|
864
|
+
// Calculate line index
|
|
865
|
+
while (lineOffsets[lineIndex + 1] <= referenceindex) {
|
|
866
|
+
lineIndex++;
|
|
867
|
+
}
|
|
868
|
+
} else {
|
|
869
|
+
// Use provided line index
|
|
870
|
+
lineIndex = contentLineIndex;
|
|
871
|
+
}
|
|
872
|
+
const referenceIndex = referenceindex +
|
|
873
|
+
(topLevel ? -lineOffsets[lineIndex] : contentIndex);
|
|
874
|
+
// Track reference and location
|
|
875
|
+
const referenceData = references.get(label) || [];
|
|
876
|
+
referenceData.push([
|
|
877
|
+
lineIndex,
|
|
878
|
+
referenceIndex,
|
|
879
|
+
matchString.length
|
|
880
|
+
]);
|
|
881
|
+
references.set(label, referenceData);
|
|
882
|
+
// Check for images embedded in top-level link text
|
|
883
|
+
if (!matchString.startsWith("!")) {
|
|
884
|
+
pendingContents.push(
|
|
885
|
+
{
|
|
886
|
+
"content": matchText,
|
|
887
|
+
"contentLineIndex": lineIndex,
|
|
888
|
+
"contentIndex": referenceIndex + 1,
|
|
889
|
+
"topLevel": false
|
|
890
|
+
}
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
references,
|
|
900
|
+
shortcuts,
|
|
901
|
+
definitions,
|
|
902
|
+
duplicateDefinitions
|
|
903
|
+
};
|
|
635
904
|
}
|
|
636
|
-
module.exports.
|
|
905
|
+
module.exports.getReferenceLinkImageData = getReferenceLinkImageData;
|
|
637
906
|
|
|
638
907
|
/**
|
|
639
908
|
* Gets the most common line ending, falling back to the platform default.
|
|
640
909
|
*
|
|
641
910
|
* @param {string} input Markdown content to analyze.
|
|
911
|
+
* @param {Object} [os] Node.js "os" module.
|
|
642
912
|
* @returns {string} Preferred line ending.
|
|
643
913
|
*/
|
|
644
|
-
function getPreferredLineEnding(input) {
|
|
914
|
+
function getPreferredLineEnding(input, os) {
|
|
645
915
|
let cr = 0;
|
|
646
916
|
let lf = 0;
|
|
647
917
|
let crlf = 0;
|
|
648
918
|
const endings = input.match(newLineRe) || [];
|
|
649
|
-
|
|
919
|
+
for (const ending of endings) {
|
|
650
920
|
// eslint-disable-next-line default-case
|
|
651
921
|
switch (ending) {
|
|
652
922
|
case "\r":
|
|
@@ -659,10 +929,10 @@ function getPreferredLineEnding(input) {
|
|
|
659
929
|
crlf++;
|
|
660
930
|
break;
|
|
661
931
|
}
|
|
662
|
-
}
|
|
932
|
+
}
|
|
663
933
|
let preferredLineEnding = null;
|
|
664
934
|
if (!cr && !lf && !crlf) {
|
|
665
|
-
preferredLineEnding = os.EOL;
|
|
935
|
+
preferredLineEnding = (os && os.EOL) || "\n";
|
|
666
936
|
} else if ((lf >= crlf) && (lf >= cr)) {
|
|
667
937
|
preferredLineEnding = "\n";
|
|
668
938
|
} else if (crlf >= cr) {
|
|
@@ -695,8 +965,8 @@ function normalizeFixInfo(fixInfo, lineNumber) {
|
|
|
695
965
|
*
|
|
696
966
|
* @param {string} line Line of Markdown content.
|
|
697
967
|
* @param {Object} fixInfo RuleOnErrorFixInfo instance.
|
|
698
|
-
* @param {string} lineEnding Line ending to use.
|
|
699
|
-
* @returns {string} Fixed content.
|
|
968
|
+
* @param {string} [lineEnding] Line ending to use.
|
|
969
|
+
* @returns {string | null} Fixed content.
|
|
700
970
|
*/
|
|
701
971
|
function applyFix(line, fixInfo, lineEnding) {
|
|
702
972
|
const { editColumn, deleteCount, insertText } = normalizeFixInfo(fixInfo);
|
|
@@ -709,9 +979,15 @@ function applyFix(line, fixInfo, lineEnding) {
|
|
|
709
979
|
}
|
|
710
980
|
module.exports.applyFix = applyFix;
|
|
711
981
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
982
|
+
/**
|
|
983
|
+
* Applies as many fixes as possible to Markdown content.
|
|
984
|
+
*
|
|
985
|
+
* @param {string} input Lines of Markdown content.
|
|
986
|
+
* @param {Object[]} errors RuleOnErrorInfo instances.
|
|
987
|
+
* @returns {string} Corrected content.
|
|
988
|
+
*/
|
|
989
|
+
function applyFixes(input, errors) {
|
|
990
|
+
const lineEnding = getPreferredLineEnding(input, require("os"));
|
|
715
991
|
const lines = input.split(newLineRe);
|
|
716
992
|
// Normalize fixInfo objects
|
|
717
993
|
let fixInfos = errors
|
|
@@ -741,8 +1017,10 @@ module.exports.applyFixes = function applyFixes(input, errors) {
|
|
|
741
1017
|
return unique;
|
|
742
1018
|
});
|
|
743
1019
|
// Collapse insert/no-delete and no-insert/delete for same line/column
|
|
744
|
-
lastFixInfo = {
|
|
745
|
-
|
|
1020
|
+
lastFixInfo = {
|
|
1021
|
+
"lineNumber": -1
|
|
1022
|
+
};
|
|
1023
|
+
for (const fixInfo of fixInfos) {
|
|
746
1024
|
if (
|
|
747
1025
|
(fixInfo.lineNumber === lastFixInfo.lineNumber) &&
|
|
748
1026
|
(fixInfo.editColumn === lastFixInfo.editColumn) &&
|
|
@@ -754,12 +1032,12 @@ module.exports.applyFixes = function applyFixes(input, errors) {
|
|
|
754
1032
|
lastFixInfo.lineNumber = 0;
|
|
755
1033
|
}
|
|
756
1034
|
lastFixInfo = fixInfo;
|
|
757
|
-
}
|
|
1035
|
+
}
|
|
758
1036
|
fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber);
|
|
759
1037
|
// Apply all (remaining/updated) fixes
|
|
760
1038
|
let lastLineIndex = -1;
|
|
761
1039
|
let lastEditIndex = -1;
|
|
762
|
-
|
|
1040
|
+
for (const fixInfo of fixInfos) {
|
|
763
1041
|
const { lineNumber, editColumn, deleteCount } = fixInfo;
|
|
764
1042
|
const lineIndex = lineNumber - 1;
|
|
765
1043
|
const editIndex = editColumn - 1;
|
|
@@ -769,11 +1047,86 @@ module.exports.applyFixes = function applyFixes(input, errors) {
|
|
|
769
1047
|
((editIndex + deleteCount) <=
|
|
770
1048
|
(lastEditIndex - ((deleteCount > 0) ? 0 : 1)))
|
|
771
1049
|
) {
|
|
1050
|
+
// @ts-ignore
|
|
772
1051
|
lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding);
|
|
773
1052
|
}
|
|
774
1053
|
lastLineIndex = lineIndex;
|
|
775
1054
|
lastEditIndex = editIndex;
|
|
776
|
-
}
|
|
1055
|
+
}
|
|
777
1056
|
// Return corrected input
|
|
778
1057
|
return lines.filter((line) => line !== null).join(lineEnding);
|
|
779
|
-
}
|
|
1058
|
+
}
|
|
1059
|
+
module.exports.applyFixes = applyFixes;
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Gets the range and fixInfo values for reporting an error if the expected
|
|
1063
|
+
* text is found on the specified line.
|
|
1064
|
+
*
|
|
1065
|
+
* @param {string[]} lines Lines of Markdown content.
|
|
1066
|
+
* @param {number} lineIndex Line index to check.
|
|
1067
|
+
* @param {string} search Text to search for.
|
|
1068
|
+
* @param {string} replace Text to replace with.
|
|
1069
|
+
* @param {number} [instance] Instance on the line (1-based).
|
|
1070
|
+
* @returns {Object} Range and fixInfo wrapper.
|
|
1071
|
+
*/
|
|
1072
|
+
module.exports.getRangeAndFixInfoIfFound =
|
|
1073
|
+
(lines, lineIndex, search, replace, instance = 1) => {
|
|
1074
|
+
let range = null;
|
|
1075
|
+
let fixInfo = null;
|
|
1076
|
+
let searchIndex = -1;
|
|
1077
|
+
while (instance > 0) {
|
|
1078
|
+
searchIndex = lines[lineIndex].indexOf(search, searchIndex + 1);
|
|
1079
|
+
instance--;
|
|
1080
|
+
}
|
|
1081
|
+
if (searchIndex !== -1) {
|
|
1082
|
+
const column = searchIndex + 1;
|
|
1083
|
+
const length = search.length;
|
|
1084
|
+
range = [ column, length ];
|
|
1085
|
+
fixInfo = {
|
|
1086
|
+
"editColumn": column,
|
|
1087
|
+
"deleteCount": length,
|
|
1088
|
+
"insertText": replace
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
return {
|
|
1092
|
+
range,
|
|
1093
|
+
fixInfo
|
|
1094
|
+
};
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Gets the next (subsequent) child token if it is of the expected type.
|
|
1099
|
+
*
|
|
1100
|
+
* @param {Object} parentToken Parent token.
|
|
1101
|
+
* @param {Object} childToken Child token basis.
|
|
1102
|
+
* @param {string} nextType Token type of next token.
|
|
1103
|
+
* @param {string} nextNextType Token type of next-next token.
|
|
1104
|
+
* @returns {Object} Next token.
|
|
1105
|
+
*/
|
|
1106
|
+
function getNextChildToken(parentToken, childToken, nextType, nextNextType) {
|
|
1107
|
+
const { children } = parentToken;
|
|
1108
|
+
const index = children.indexOf(childToken);
|
|
1109
|
+
if (
|
|
1110
|
+
(index !== -1) &&
|
|
1111
|
+
(children.length > index + 2) &&
|
|
1112
|
+
(children[index + 1].type === nextType) &&
|
|
1113
|
+
(children[index + 2].type === nextNextType)
|
|
1114
|
+
) {
|
|
1115
|
+
return children[index + 1];
|
|
1116
|
+
}
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
module.exports.getNextChildToken = getNextChildToken;
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Expands a path with a tilde to an absolute path.
|
|
1123
|
+
*
|
|
1124
|
+
* @param {string} file Path that may begin with a tilde.
|
|
1125
|
+
* @param {Object} os Node.js "os" module.
|
|
1126
|
+
* @returns {string} Absolute path (or original path).
|
|
1127
|
+
*/
|
|
1128
|
+
function expandTildePath(file, os) {
|
|
1129
|
+
const homedir = os && os.homedir();
|
|
1130
|
+
return homedir ? file.replace(/^~($|\/|\\)/, `${homedir}$1`) : file;
|
|
1131
|
+
}
|
|
1132
|
+
module.exports.expandTildePath = expandTildePath;
|