markdownlint 0.22.0 → 0.25.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 +13 -3
- package/LICENSE +1 -1
- package/README.md +140 -83
- package/demo/markdownlint-browser.js +1599 -3309
- package/doc/CustomRules.md +15 -4
- package/doc/Prettier.md +23 -0
- package/doc/Rules.md +191 -80
- package/helpers/LICENSE +1 -1
- package/helpers/helpers.js +260 -68
- package/helpers/package.json +1 -1
- package/lib/cache.js +14 -5
- package/lib/constants.js +7 -0
- package/lib/markdownlint.d.ts +31 -11
- package/lib/markdownlint.js +336 -254
- package/lib/md002.js +1 -1
- package/lib/md004.js +46 -13
- package/lib/md006.js +1 -1
- package/lib/md007.js +4 -2
- package/lib/md009.js +24 -20
- package/lib/md010.js +25 -16
- package/lib/md011.js +31 -27
- package/lib/md018.js +1 -1
- package/lib/md019.js +1 -1
- package/lib/md020.js +1 -1
- package/lib/md021.js +1 -1
- package/lib/md023.js +1 -1
- package/lib/md025.js +1 -1
- package/lib/md032.js +2 -2
- package/lib/md033.js +12 -8
- package/lib/md035.js +4 -4
- package/lib/md037.js +40 -12
- package/lib/md039.js +4 -3
- package/lib/md041.js +14 -3
- package/lib/md043.js +9 -5
- package/lib/md044.js +54 -71
- package/lib/md049.js +45 -0
- package/lib/md050.js +45 -0
- package/lib/rules.js +6 -5
- package/package.json +46 -37
- package/schema/.markdownlint.jsonc +266 -0
- package/schema/.markdownlint.yaml +239 -0
- package/schema/markdownlint-config-schema.json +129 -22
- package/schema/build-config-schema.js +0 -417
package/lib/markdownlint.js
CHANGED
|
@@ -2,23 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
-
const fs = require("fs");
|
|
6
5
|
const path = require("path");
|
|
7
6
|
const { promisify } = require("util");
|
|
8
7
|
const markdownIt = require("markdown-it");
|
|
8
|
+
const { deprecatedRuleNames } = require("./constants");
|
|
9
9
|
const rules = require("./rules");
|
|
10
10
|
const helpers = require("../helpers");
|
|
11
11
|
const cache = require("./cache");
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
// eslint-disable-next-line camelcase, max-len, no-inline-comments, no-undef
|
|
15
|
+
const dynamicRequire = (typeof __non_webpack_require__ === "undefined") ? require : /* c8 ignore next */ __non_webpack_require__;
|
|
16
|
+
// Capture native require implementation for dynamic loading of modules
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* Validate the list of rules for structure and reuse.
|
|
17
20
|
*
|
|
18
21
|
* @param {Rule[]} ruleList List of rules.
|
|
22
|
+
* @param {boolean} synchronous Whether to execute synchronously.
|
|
19
23
|
* @returns {string} Error message if validation fails.
|
|
20
24
|
*/
|
|
21
|
-
function validateRuleList(ruleList) {
|
|
25
|
+
function validateRuleList(ruleList, synchronous) {
|
|
22
26
|
let result = null;
|
|
23
27
|
if (ruleList.length === rules.length) {
|
|
24
28
|
// No need to validate if only using built-in rules
|
|
@@ -51,10 +55,25 @@ function validateRuleList(ruleList) {
|
|
|
51
55
|
result = newError(property);
|
|
52
56
|
}
|
|
53
57
|
});
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
if (
|
|
59
|
+
!result &&
|
|
60
|
+
rule.information &&
|
|
61
|
+
(Object.getPrototypeOf(rule.information) !== URL.prototype)
|
|
62
|
+
) {
|
|
63
|
+
result = newError("information");
|
|
64
|
+
}
|
|
65
|
+
if (
|
|
66
|
+
!result &&
|
|
67
|
+
(rule.asynchronous !== undefined) &&
|
|
68
|
+
(typeof rule.asynchronous !== "boolean")
|
|
69
|
+
) {
|
|
70
|
+
result = newError("asynchronous");
|
|
71
|
+
}
|
|
72
|
+
if (!result && rule.asynchronous && synchronous) {
|
|
73
|
+
result = new Error(
|
|
74
|
+
"Custom rule " + rule.names.join("/") + " at index " + customIndex +
|
|
75
|
+
" is asynchronous and can not be used in a synchronous context."
|
|
76
|
+
);
|
|
58
77
|
}
|
|
59
78
|
if (!result) {
|
|
60
79
|
rule.names.forEach(function forName(name) {
|
|
@@ -176,27 +195,21 @@ function removeFrontMatter(content, frontMatter) {
|
|
|
176
195
|
* @returns {void}
|
|
177
196
|
*/
|
|
178
197
|
function annotateTokens(tokens, lines) {
|
|
179
|
-
let
|
|
198
|
+
let trMap = null;
|
|
180
199
|
tokens.forEach(function forToken(token) {
|
|
181
|
-
//
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
tableMap
|
|
190
|
-
) {
|
|
191
|
-
tableMap[0]++;
|
|
192
|
-
} else if (
|
|
193
|
-
(token.type === "thead_close") ||
|
|
194
|
-
(token.type === "tbody_close")
|
|
195
|
-
) {
|
|
196
|
-
tableMap = null;
|
|
200
|
+
// Provide missing maps for table content
|
|
201
|
+
if (token.type === "tr_open") {
|
|
202
|
+
trMap = token.map;
|
|
203
|
+
} else if (token.type === "tr_close") {
|
|
204
|
+
trMap = null;
|
|
205
|
+
}
|
|
206
|
+
if (!token.map && trMap) {
|
|
207
|
+
token.map = [ ...trMap ];
|
|
197
208
|
}
|
|
198
|
-
|
|
199
|
-
|
|
209
|
+
// Adjust maps for math blocks
|
|
210
|
+
if (helpers.isMathBlock(token) && token.map[1]) {
|
|
211
|
+
// markdown-it-texmath plugin does not account for math_block_end
|
|
212
|
+
token.map[1]++;
|
|
200
213
|
}
|
|
201
214
|
// Update token metadata
|
|
202
215
|
if (token.map) {
|
|
@@ -329,8 +342,7 @@ function getEnabledRulesPerLineNumber(
|
|
|
329
342
|
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
|
|
330
343
|
// Helper functions
|
|
331
344
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
332
|
-
function handleInlineConfig(
|
|
333
|
-
const input = perLine ? lines : [ lines.join("\n") ];
|
|
345
|
+
function handleInlineConfig(input, forEachMatch, forEachLine) {
|
|
334
346
|
input.forEach((line, lineIndex) => {
|
|
335
347
|
if (!noInlineConfig) {
|
|
336
348
|
let match = null;
|
|
@@ -361,6 +373,7 @@ function getEnabledRulesPerLineNumber(
|
|
|
361
373
|
}
|
|
362
374
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
363
375
|
function applyEnableDisable(action, parameter, state) {
|
|
376
|
+
state = { ...state };
|
|
364
377
|
const enabled = (action.startsWith("ENABLE"));
|
|
365
378
|
const items = parameter ?
|
|
366
379
|
parameter.trim().toUpperCase().split(/\s+/) :
|
|
@@ -370,40 +383,42 @@ function getEnabledRulesPerLineNumber(
|
|
|
370
383
|
state[ruleName] = enabled;
|
|
371
384
|
});
|
|
372
385
|
});
|
|
386
|
+
return state;
|
|
373
387
|
}
|
|
374
388
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
375
389
|
function enableDisableFile(action, parameter) {
|
|
376
390
|
if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
|
|
377
|
-
applyEnableDisable(action, parameter, enabledRules);
|
|
391
|
+
enabledRules = applyEnableDisable(action, parameter, enabledRules);
|
|
378
392
|
}
|
|
379
393
|
}
|
|
380
394
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
381
395
|
function captureRestoreEnableDisable(action, parameter) {
|
|
382
396
|
if (action === "CAPTURE") {
|
|
383
|
-
capturedRules =
|
|
397
|
+
capturedRules = enabledRules;
|
|
384
398
|
} else if (action === "RESTORE") {
|
|
385
|
-
enabledRules =
|
|
399
|
+
enabledRules = capturedRules;
|
|
386
400
|
} else if ((action === "ENABLE") || (action === "DISABLE")) {
|
|
387
|
-
enabledRules =
|
|
388
|
-
applyEnableDisable(action, parameter, enabledRules);
|
|
401
|
+
enabledRules = applyEnableDisable(action, parameter, enabledRules);
|
|
389
402
|
}
|
|
390
403
|
}
|
|
391
404
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
392
405
|
function updateLineState() {
|
|
393
|
-
enabledRulesPerLineNumber.push(
|
|
406
|
+
enabledRulesPerLineNumber.push(enabledRules);
|
|
394
407
|
}
|
|
395
408
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
396
409
|
function disableNextLine(action, parameter, lineNumber) {
|
|
397
410
|
if (action === "DISABLE-NEXT-LINE") {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
411
|
+
const nextLineNumber = frontMatterLines.length + lineNumber + 1;
|
|
412
|
+
enabledRulesPerLineNumber[nextLineNumber] =
|
|
413
|
+
applyEnableDisable(
|
|
414
|
+
action,
|
|
415
|
+
parameter,
|
|
416
|
+
enabledRulesPerLineNumber[nextLineNumber] || {}
|
|
417
|
+
);
|
|
403
418
|
}
|
|
404
419
|
}
|
|
405
420
|
// Handle inline comments
|
|
406
|
-
handleInlineConfig(
|
|
421
|
+
handleInlineConfig([ lines.join("\n") ], configureFile);
|
|
407
422
|
const effectiveConfig = getEffectiveConfig(
|
|
408
423
|
ruleList, config, aliasToRuleNames);
|
|
409
424
|
ruleList.forEach((rule) => {
|
|
@@ -412,9 +427,9 @@ function getEnabledRulesPerLineNumber(
|
|
|
412
427
|
enabledRules[ruleName] = !!effectiveConfig[ruleName];
|
|
413
428
|
});
|
|
414
429
|
capturedRules = enabledRules;
|
|
415
|
-
handleInlineConfig(
|
|
416
|
-
handleInlineConfig(
|
|
417
|
-
handleInlineConfig(
|
|
430
|
+
handleInlineConfig(lines, enableDisableFile);
|
|
431
|
+
handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
|
|
432
|
+
handleInlineConfig(lines, disableNextLine);
|
|
418
433
|
// Return results
|
|
419
434
|
return {
|
|
420
435
|
effectiveConfig,
|
|
@@ -422,38 +437,6 @@ function getEnabledRulesPerLineNumber(
|
|
|
422
437
|
};
|
|
423
438
|
}
|
|
424
439
|
|
|
425
|
-
/**
|
|
426
|
-
* Compare function for Array.prototype.sort for ascending order of errors.
|
|
427
|
-
*
|
|
428
|
-
* @param {LintError} a First error.
|
|
429
|
-
* @param {LintError} b Second error.
|
|
430
|
-
* @returns {number} Positive value if a>b, negative value if b<a, 0 otherwise.
|
|
431
|
-
*/
|
|
432
|
-
function lineNumberComparison(a, b) {
|
|
433
|
-
return a.lineNumber - b.lineNumber;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Filter function to include everything.
|
|
438
|
-
*
|
|
439
|
-
* @returns {boolean} True.
|
|
440
|
-
*/
|
|
441
|
-
function filterAllValues() {
|
|
442
|
-
return true;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Function to return unique values from a sorted errors array.
|
|
447
|
-
*
|
|
448
|
-
* @param {LintError} value Error instance.
|
|
449
|
-
* @param {number} index Index in array.
|
|
450
|
-
* @param {LintError[]} array Array of errors.
|
|
451
|
-
* @returns {boolean} Filter value.
|
|
452
|
-
*/
|
|
453
|
-
function uniqueFilterForSortedErrors(value, index, array) {
|
|
454
|
-
return (index === 0) || (value.lineNumber > array[index - 1].lineNumber);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
440
|
/**
|
|
458
441
|
* Lints a string containing Markdown content.
|
|
459
442
|
*
|
|
@@ -503,27 +486,28 @@ function lintContent(
|
|
|
503
486
|
);
|
|
504
487
|
// Create parameters for rules
|
|
505
488
|
const params = {
|
|
506
|
-
name,
|
|
507
|
-
tokens,
|
|
508
|
-
lines,
|
|
509
|
-
frontMatterLines
|
|
489
|
+
"name": helpers.deepFreeze(name),
|
|
490
|
+
"tokens": helpers.deepFreeze(tokens),
|
|
491
|
+
"lines": helpers.deepFreeze(lines),
|
|
492
|
+
"frontMatterLines": helpers.deepFreeze(frontMatterLines)
|
|
510
493
|
};
|
|
511
494
|
cache.lineMetadata(helpers.getLineMetadata(params));
|
|
512
|
-
cache.flattenedLists(helpers.flattenLists(params));
|
|
495
|
+
cache.flattenedLists(helpers.flattenLists(params.tokens));
|
|
496
|
+
cache.codeBlockAndSpanRanges(
|
|
497
|
+
helpers.codeBlockAndSpanRanges(params, cache.lineMetadata())
|
|
498
|
+
);
|
|
513
499
|
// Function to run for each rule
|
|
514
|
-
|
|
500
|
+
let results = [];
|
|
515
501
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
516
502
|
function forRule(rule) {
|
|
517
503
|
// Configure rule
|
|
518
|
-
const
|
|
519
|
-
const ruleName = ruleNameFriendly.toUpperCase();
|
|
504
|
+
const ruleName = rule.names[0].toUpperCase();
|
|
520
505
|
params.config = effectiveConfig[ruleName];
|
|
521
506
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
522
507
|
function throwError(property) {
|
|
523
508
|
throw new Error(
|
|
524
509
|
"Property '" + property + "' of onError parameter is incorrect.");
|
|
525
510
|
}
|
|
526
|
-
const errors = [];
|
|
527
511
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
528
512
|
function onError(errorInfo) {
|
|
529
513
|
if (!errorInfo ||
|
|
@@ -532,6 +516,10 @@ function lintContent(
|
|
|
532
516
|
(errorInfo.lineNumber > lines.length)) {
|
|
533
517
|
throwError("lineNumber");
|
|
534
518
|
}
|
|
519
|
+
const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
|
|
520
|
+
if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
535
523
|
if (errorInfo.detail &&
|
|
536
524
|
!helpers.isString(errorInfo.detail)) {
|
|
537
525
|
throwError("detail");
|
|
@@ -542,12 +530,12 @@ function lintContent(
|
|
|
542
530
|
}
|
|
543
531
|
if (errorInfo.range &&
|
|
544
532
|
(!Array.isArray(errorInfo.range) ||
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
533
|
+
(errorInfo.range.length !== 2) ||
|
|
534
|
+
!helpers.isNumber(errorInfo.range[0]) ||
|
|
535
|
+
(errorInfo.range[0] < 1) ||
|
|
536
|
+
!helpers.isNumber(errorInfo.range[1]) ||
|
|
537
|
+
(errorInfo.range[1] < 1) ||
|
|
538
|
+
((errorInfo.range[0] + errorInfo.range[1] - 1) >
|
|
551
539
|
lines[errorInfo.lineNumber - 1].length))) {
|
|
552
540
|
throwError("range");
|
|
553
541
|
}
|
|
@@ -592,78 +580,114 @@ function lintContent(
|
|
|
592
580
|
cleanFixInfo.insertText = fixInfo.insertText;
|
|
593
581
|
}
|
|
594
582
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
"
|
|
598
|
-
"
|
|
599
|
-
"
|
|
583
|
+
results.push({
|
|
584
|
+
lineNumber,
|
|
585
|
+
"ruleName": rule.names[0],
|
|
586
|
+
"ruleNames": rule.names,
|
|
587
|
+
"ruleDescription": rule.description,
|
|
588
|
+
"ruleInformation": rule.information ? rule.information.href : null,
|
|
589
|
+
"errorDetail": errorInfo.detail || null,
|
|
590
|
+
"errorContext": errorInfo.context || null,
|
|
591
|
+
"errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
|
|
600
592
|
"fixInfo": fixInfo ? cleanFixInfo : null
|
|
601
593
|
});
|
|
602
594
|
}
|
|
603
|
-
// Call (possibly external) rule function
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
595
|
+
// Call (possibly external) rule function to report errors
|
|
596
|
+
const catchCallsOnError = (error) => onError({
|
|
597
|
+
"lineNumber": 1,
|
|
598
|
+
"detail": `This rule threw an exception: ${error.message || error}`
|
|
599
|
+
});
|
|
600
|
+
const invokeRuleFunction = () => rule.function(params, onError);
|
|
601
|
+
if (rule.asynchronous) {
|
|
602
|
+
// Asynchronous rule, ensure it returns a Promise
|
|
603
|
+
const ruleFunctionPromise =
|
|
604
|
+
Promise.resolve().then(invokeRuleFunction);
|
|
605
|
+
return handleRuleFailures ?
|
|
606
|
+
ruleFunctionPromise.catch(catchCallsOnError) :
|
|
607
|
+
ruleFunctionPromise;
|
|
608
|
+
}
|
|
609
|
+
// Synchronous rule
|
|
610
|
+
try {
|
|
611
|
+
invokeRuleFunction();
|
|
612
|
+
} catch (error) {
|
|
613
|
+
if (handleRuleFailures) {
|
|
614
|
+
catchCallsOnError(error);
|
|
615
|
+
} else {
|
|
616
|
+
throw error;
|
|
612
617
|
}
|
|
613
|
-
} else {
|
|
614
|
-
rule.function(params, onError);
|
|
615
618
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
622
|
+
function formatResults() {
|
|
623
|
+
// Sort results by rule name by line number
|
|
624
|
+
results.sort((a, b) => (
|
|
625
|
+
a.ruleName.localeCompare(b.ruleName) ||
|
|
626
|
+
a.lineNumber - b.lineNumber
|
|
627
|
+
));
|
|
628
|
+
if (resultVersion < 3) {
|
|
629
|
+
// Remove fixInfo and multiple errors for the same rule and line number
|
|
630
|
+
const noPrevious = {
|
|
631
|
+
"ruleName": null,
|
|
632
|
+
"lineNumber": -1
|
|
633
|
+
};
|
|
634
|
+
results = results.filter((error, index, array) => {
|
|
635
|
+
delete error.fixInfo;
|
|
636
|
+
const previous = array[index - 1] || noPrevious;
|
|
637
|
+
return (
|
|
638
|
+
(error.ruleName !== previous.ruleName) ||
|
|
639
|
+
(error.lineNumber !== previous.lineNumber)
|
|
640
|
+
);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
if (resultVersion === 0) {
|
|
644
|
+
// Return a dictionary of rule->[line numbers]
|
|
645
|
+
const dictionary = {};
|
|
646
|
+
for (const error of results) {
|
|
647
|
+
const ruleLines = dictionary[error.ruleName] || [];
|
|
648
|
+
ruleLines.push(error.lineNumber);
|
|
649
|
+
dictionary[error.ruleName] = ruleLines;
|
|
650
|
+
}
|
|
651
|
+
// @ts-ignore
|
|
652
|
+
results = dictionary;
|
|
653
|
+
} else if (resultVersion === 1) {
|
|
654
|
+
// Use ruleAlias instead of ruleNames
|
|
655
|
+
for (const error of results) {
|
|
656
|
+
error.ruleAlias = error.ruleNames[1] || error.ruleName;
|
|
657
|
+
delete error.ruleNames;
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
// resultVersion 2 or 3: Remove unwanted ruleName
|
|
661
|
+
for (const error of results) {
|
|
662
|
+
delete error.ruleName;
|
|
655
663
|
}
|
|
656
664
|
}
|
|
665
|
+
return results;
|
|
657
666
|
}
|
|
658
667
|
// Run all rules
|
|
668
|
+
const ruleListAsync = ruleList.filter((rule) => rule.asynchronous);
|
|
669
|
+
const ruleListSync = ruleList.filter((rule) => !rule.asynchronous);
|
|
670
|
+
const ruleListAsyncFirst = [
|
|
671
|
+
...ruleListAsync,
|
|
672
|
+
...ruleListSync
|
|
673
|
+
];
|
|
674
|
+
const callbackSuccess = () => callback(null, formatResults());
|
|
675
|
+
const callbackError =
|
|
676
|
+
(error) => callback(error instanceof Error ? error : new Error(error));
|
|
659
677
|
try {
|
|
660
|
-
|
|
678
|
+
const ruleResults = ruleListAsyncFirst.map(forRule);
|
|
679
|
+
if (ruleListAsync.length > 0) {
|
|
680
|
+
Promise.all(ruleResults.slice(0, ruleListAsync.length))
|
|
681
|
+
.then(callbackSuccess)
|
|
682
|
+
.catch(callbackError);
|
|
683
|
+
} else {
|
|
684
|
+
callbackSuccess();
|
|
685
|
+
}
|
|
661
686
|
} catch (error) {
|
|
687
|
+
callbackError(error);
|
|
688
|
+
} finally {
|
|
662
689
|
cache.clear();
|
|
663
|
-
return callback(error);
|
|
664
690
|
}
|
|
665
|
-
cache.clear();
|
|
666
|
-
return callback(null, result);
|
|
667
691
|
}
|
|
668
692
|
|
|
669
693
|
/**
|
|
@@ -677,6 +701,7 @@ function lintContent(
|
|
|
677
701
|
* @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
|
|
678
702
|
* @param {boolean} noInlineConfig Whether to allow inline configuration.
|
|
679
703
|
* @param {number} resultVersion Version of the LintResults object to return.
|
|
704
|
+
* @param {Object} fs File system implementation.
|
|
680
705
|
* @param {boolean} synchronous Whether to execute synchronously.
|
|
681
706
|
* @param {Function} callback Callback (err, result) function.
|
|
682
707
|
* @returns {void}
|
|
@@ -690,6 +715,7 @@ function lintFile(
|
|
|
690
715
|
handleRuleFailures,
|
|
691
716
|
noInlineConfig,
|
|
692
717
|
resultVersion,
|
|
718
|
+
fs,
|
|
693
719
|
synchronous,
|
|
694
720
|
callback) {
|
|
695
721
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
@@ -702,10 +728,9 @@ function lintFile(
|
|
|
702
728
|
}
|
|
703
729
|
// Make a/synchronous call to read file
|
|
704
730
|
if (synchronous) {
|
|
705
|
-
|
|
706
|
-
lintContentWrapper(null, fs.readFileSync(file, helpers.utf8Encoding));
|
|
731
|
+
lintContentWrapper(null, fs.readFileSync(file, "utf8"));
|
|
707
732
|
} else {
|
|
708
|
-
fs.readFile(file,
|
|
733
|
+
fs.readFile(file, "utf8", lintContentWrapper);
|
|
709
734
|
}
|
|
710
735
|
}
|
|
711
736
|
|
|
@@ -721,14 +746,15 @@ function lintInput(options, synchronous, callback) {
|
|
|
721
746
|
// Normalize inputs
|
|
722
747
|
options = options || {};
|
|
723
748
|
callback = callback || function noop() {};
|
|
749
|
+
// eslint-disable-next-line unicorn/prefer-spread
|
|
724
750
|
const ruleList = rules.concat(options.customRules || []);
|
|
725
|
-
const ruleErr = validateRuleList(ruleList);
|
|
751
|
+
const ruleErr = validateRuleList(ruleList, synchronous);
|
|
726
752
|
if (ruleErr) {
|
|
727
753
|
return callback(ruleErr);
|
|
728
754
|
}
|
|
729
755
|
let files = [];
|
|
730
756
|
if (Array.isArray(options.files)) {
|
|
731
|
-
files = options.files
|
|
757
|
+
files = [ ...options.files ];
|
|
732
758
|
} else if (options.files) {
|
|
733
759
|
files = [ String(options.files) ];
|
|
734
760
|
}
|
|
@@ -747,98 +773,85 @@ function lintInput(options, synchronous, callback) {
|
|
|
747
773
|
// @ts-ignore
|
|
748
774
|
md.use(...plugin);
|
|
749
775
|
});
|
|
776
|
+
const fs = options.fs || require("fs");
|
|
750
777
|
const results = newResults(ruleList);
|
|
751
778
|
let done = false;
|
|
752
|
-
|
|
753
|
-
let syncItem = null;
|
|
779
|
+
let concurrency = 0;
|
|
754
780
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
755
|
-
function
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
781
|
+
function lintWorker() {
|
|
782
|
+
let currentItem = null;
|
|
783
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
784
|
+
function lintWorkerCallback(err, result) {
|
|
785
|
+
concurrency--;
|
|
786
|
+
if (err) {
|
|
787
|
+
done = true;
|
|
788
|
+
return callback(err);
|
|
789
|
+
}
|
|
790
|
+
results[currentItem] = result;
|
|
791
|
+
if (!synchronous) {
|
|
792
|
+
lintWorker();
|
|
793
|
+
}
|
|
794
|
+
return null;
|
|
759
795
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
syncItem,
|
|
767
|
-
strings[syncItem] || "",
|
|
768
|
-
md,
|
|
769
|
-
config,
|
|
770
|
-
frontMatter,
|
|
771
|
-
handleRuleFailures,
|
|
772
|
-
noInlineConfig,
|
|
773
|
-
resultVersion,
|
|
774
|
-
syncCallback
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
if (synchronous) {
|
|
778
|
-
// Lint files synchronously
|
|
779
|
-
while (!done && (syncItem = files.shift())) {
|
|
796
|
+
if (done) {
|
|
797
|
+
// Abort for error or nothing left to do
|
|
798
|
+
} else if (files.length > 0) {
|
|
799
|
+
// Lint next file
|
|
800
|
+
concurrency++;
|
|
801
|
+
currentItem = files.shift();
|
|
780
802
|
lintFile(
|
|
781
803
|
ruleList,
|
|
782
|
-
|
|
804
|
+
currentItem,
|
|
783
805
|
md,
|
|
784
806
|
config,
|
|
785
807
|
frontMatter,
|
|
786
808
|
handleRuleFailures,
|
|
787
809
|
noInlineConfig,
|
|
788
810
|
resultVersion,
|
|
811
|
+
fs,
|
|
789
812
|
synchronous,
|
|
790
|
-
|
|
813
|
+
lintWorkerCallback
|
|
791
814
|
);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
// Lint files asynchronously
|
|
796
|
-
let concurrency = 0;
|
|
797
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
798
|
-
function lintConcurrently() {
|
|
799
|
-
const asyncItem = files.shift();
|
|
800
|
-
if (done) {
|
|
801
|
-
// Nothing to do
|
|
802
|
-
} else if (asyncItem) {
|
|
815
|
+
} else if (stringsKeys.length > 0) {
|
|
816
|
+
// Lint next string
|
|
803
817
|
concurrency++;
|
|
804
|
-
|
|
818
|
+
currentItem = stringsKeys.shift();
|
|
819
|
+
lintContent(
|
|
805
820
|
ruleList,
|
|
806
|
-
|
|
821
|
+
currentItem,
|
|
822
|
+
strings[currentItem] || "",
|
|
807
823
|
md,
|
|
808
824
|
config,
|
|
809
825
|
frontMatter,
|
|
810
826
|
handleRuleFailures,
|
|
811
827
|
noInlineConfig,
|
|
812
828
|
resultVersion,
|
|
813
|
-
|
|
814
|
-
(err, result) => {
|
|
815
|
-
concurrency--;
|
|
816
|
-
if (err) {
|
|
817
|
-
done = true;
|
|
818
|
-
return callback(err);
|
|
819
|
-
}
|
|
820
|
-
results[asyncItem] = result;
|
|
821
|
-
lintConcurrently();
|
|
822
|
-
return null;
|
|
823
|
-
}
|
|
829
|
+
lintWorkerCallback
|
|
824
830
|
);
|
|
825
831
|
} else if (concurrency === 0) {
|
|
832
|
+
// Finish
|
|
826
833
|
done = true;
|
|
827
834
|
return callback(null, results);
|
|
828
835
|
}
|
|
829
836
|
return null;
|
|
830
837
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
838
|
+
if (synchronous) {
|
|
839
|
+
while (!done) {
|
|
840
|
+
lintWorker();
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
// Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access
|
|
844
|
+
// delay suggests that a concurrency factor of 8 can eliminate the impact
|
|
845
|
+
// of that delay (i.e., total time is the same as with no delay).
|
|
846
|
+
lintWorker();
|
|
847
|
+
lintWorker();
|
|
848
|
+
lintWorker();
|
|
849
|
+
lintWorker();
|
|
850
|
+
lintWorker();
|
|
851
|
+
lintWorker();
|
|
852
|
+
lintWorker();
|
|
853
|
+
lintWorker();
|
|
854
|
+
}
|
|
842
855
|
return null;
|
|
843
856
|
}
|
|
844
857
|
|
|
@@ -853,7 +866,7 @@ function markdownlint(options, callback) {
|
|
|
853
866
|
return lintInput(options, false, callback);
|
|
854
867
|
}
|
|
855
868
|
|
|
856
|
-
const markdownlintPromisify = promisify(markdownlint);
|
|
869
|
+
const markdownlintPromisify = promisify && promisify(markdownlint);
|
|
857
870
|
|
|
858
871
|
/**
|
|
859
872
|
* Lint specified Markdown files.
|
|
@@ -894,12 +907,13 @@ function parseConfiguration(name, content, parsers) {
|
|
|
894
907
|
let config = null;
|
|
895
908
|
let message = "";
|
|
896
909
|
const errors = [];
|
|
910
|
+
let index = 0;
|
|
897
911
|
// Try each parser
|
|
898
912
|
(parsers || [ JSON.parse ]).every((parser) => {
|
|
899
913
|
try {
|
|
900
914
|
config = parser(content);
|
|
901
915
|
} catch (error) {
|
|
902
|
-
errors.push(error.message);
|
|
916
|
+
errors.push(`Parser ${index++}: ${error.message}`);
|
|
903
917
|
}
|
|
904
918
|
return !config;
|
|
905
919
|
});
|
|
@@ -920,22 +934,55 @@ function parseConfiguration(name, content, parsers) {
|
|
|
920
934
|
*
|
|
921
935
|
* @param {string} configFile Configuration file name.
|
|
922
936
|
* @param {string} referenceId Referenced identifier to resolve.
|
|
937
|
+
* @param {Object} fs File system implementation.
|
|
938
|
+
* @param {ResolveConfigExtendsCallback} [callback] Callback (err, result)
|
|
939
|
+
* function.
|
|
940
|
+
* @returns {void}
|
|
941
|
+
*/
|
|
942
|
+
function resolveConfigExtends(configFile, referenceId, fs, callback) {
|
|
943
|
+
const configFileDirname = path.dirname(configFile);
|
|
944
|
+
const resolvedExtendsFile = path.resolve(configFileDirname, referenceId);
|
|
945
|
+
fs.access(resolvedExtendsFile, (err) => {
|
|
946
|
+
if (err) {
|
|
947
|
+
// Not a file, try require.resolve
|
|
948
|
+
try {
|
|
949
|
+
return callback(null, dynamicRequire.resolve(
|
|
950
|
+
referenceId,
|
|
951
|
+
{ "paths": [ configFileDirname ] }
|
|
952
|
+
));
|
|
953
|
+
} catch {
|
|
954
|
+
// Unable to resolve, use resolvedExtendsFile
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return callback(null, resolvedExtendsFile);
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Resolve referenced "extends" path in a configuration file
|
|
963
|
+
* using path.resolve() with require.resolve() as a fallback.
|
|
964
|
+
*
|
|
965
|
+
* @param {string} configFile Configuration file name.
|
|
966
|
+
* @param {string} referenceId Referenced identifier to resolve.
|
|
967
|
+
* @param {Object} fs File system implementation.
|
|
923
968
|
* @returns {string} Resolved path to file.
|
|
924
969
|
*/
|
|
925
|
-
function
|
|
970
|
+
function resolveConfigExtendsSync(configFile, referenceId, fs) {
|
|
926
971
|
const configFileDirname = path.dirname(configFile);
|
|
927
972
|
const resolvedExtendsFile = path.resolve(configFileDirname, referenceId);
|
|
928
973
|
try {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
974
|
+
fs.accessSync(resolvedExtendsFile);
|
|
975
|
+
return resolvedExtendsFile;
|
|
932
976
|
} catch {
|
|
933
|
-
//
|
|
977
|
+
// Not a file, try require.resolve
|
|
934
978
|
}
|
|
935
979
|
try {
|
|
936
|
-
return
|
|
980
|
+
return dynamicRequire.resolve(
|
|
981
|
+
referenceId,
|
|
982
|
+
{ "paths": [ configFileDirname ] }
|
|
983
|
+
);
|
|
937
984
|
} catch {
|
|
938
|
-
//
|
|
985
|
+
// Unable to resolve, return resolvedExtendsFile
|
|
939
986
|
}
|
|
940
987
|
return resolvedExtendsFile;
|
|
941
988
|
}
|
|
@@ -946,17 +993,26 @@ function resolveConfigExtends(configFile, referenceId) {
|
|
|
946
993
|
* @param {string} file Configuration file name.
|
|
947
994
|
* @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing
|
|
948
995
|
* function(s).
|
|
996
|
+
* @param {Object} [fs] File system implementation.
|
|
949
997
|
* @param {ReadConfigCallback} [callback] Callback (err, result) function.
|
|
950
998
|
* @returns {void}
|
|
951
999
|
*/
|
|
952
|
-
function readConfig(file, parsers, callback) {
|
|
1000
|
+
function readConfig(file, parsers, fs, callback) {
|
|
953
1001
|
if (!callback) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1002
|
+
if (fs) {
|
|
1003
|
+
callback = fs;
|
|
1004
|
+
fs = null;
|
|
1005
|
+
} else {
|
|
1006
|
+
// @ts-ignore
|
|
1007
|
+
callback = parsers;
|
|
1008
|
+
parsers = null;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (!fs) {
|
|
1012
|
+
fs = require("fs");
|
|
957
1013
|
}
|
|
958
1014
|
// Read file
|
|
959
|
-
fs.readFile(file,
|
|
1015
|
+
fs.readFile(file, "utf8", (err, content) => {
|
|
960
1016
|
if (err) {
|
|
961
1017
|
return callback(err);
|
|
962
1018
|
}
|
|
@@ -970,33 +1026,43 @@ function readConfig(file, parsers, callback) {
|
|
|
970
1026
|
const configExtends = config.extends;
|
|
971
1027
|
if (configExtends) {
|
|
972
1028
|
delete config.extends;
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1029
|
+
return resolveConfigExtends(
|
|
1030
|
+
file,
|
|
1031
|
+
configExtends,
|
|
1032
|
+
fs,
|
|
1033
|
+
(_, resolvedExtends) => readConfig(
|
|
1034
|
+
resolvedExtends,
|
|
1035
|
+
parsers,
|
|
1036
|
+
fs,
|
|
1037
|
+
(errr, extendsConfig) => {
|
|
1038
|
+
if (errr) {
|
|
1039
|
+
return callback(errr);
|
|
1040
|
+
}
|
|
1041
|
+
return callback(null, {
|
|
1042
|
+
...extendsConfig,
|
|
1043
|
+
...config
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
983
1048
|
}
|
|
984
1049
|
return callback(null, config);
|
|
985
1050
|
});
|
|
986
1051
|
}
|
|
987
1052
|
|
|
988
|
-
const readConfigPromisify = promisify(readConfig);
|
|
1053
|
+
const readConfigPromisify = promisify && promisify(readConfig);
|
|
989
1054
|
|
|
990
1055
|
/**
|
|
991
1056
|
* Read specified configuration file.
|
|
992
1057
|
*
|
|
993
1058
|
* @param {string} file Configuration file name.
|
|
994
1059
|
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
|
|
1060
|
+
* @param {Object} [fs] File system implementation.
|
|
995
1061
|
* @returns {Promise<Configuration>} Configuration object.
|
|
996
1062
|
*/
|
|
997
|
-
function readConfigPromise(file, parsers) {
|
|
1063
|
+
function readConfigPromise(file, parsers, fs) {
|
|
998
1064
|
// @ts-ignore
|
|
999
|
-
return readConfigPromisify(file, parsers);
|
|
1065
|
+
return readConfigPromisify(file, parsers, fs);
|
|
1000
1066
|
}
|
|
1001
1067
|
|
|
1002
1068
|
/**
|
|
@@ -1004,12 +1070,16 @@ function readConfigPromise(file, parsers) {
|
|
|
1004
1070
|
*
|
|
1005
1071
|
* @param {string} file Configuration file name.
|
|
1006
1072
|
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
|
|
1073
|
+
* @param {Object} [fs] File system implementation.
|
|
1007
1074
|
* @returns {Configuration} Configuration object.
|
|
1075
|
+
* @throws An Error if processing fails.
|
|
1008
1076
|
*/
|
|
1009
|
-
function readConfigSync(file, parsers) {
|
|
1077
|
+
function readConfigSync(file, parsers, fs) {
|
|
1078
|
+
if (!fs) {
|
|
1079
|
+
fs = require("fs");
|
|
1080
|
+
}
|
|
1010
1081
|
// Read file
|
|
1011
|
-
|
|
1012
|
-
const content = fs.readFileSync(file, helpers.utf8Encoding);
|
|
1082
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1013
1083
|
// Try to parse file
|
|
1014
1084
|
const { config, message } = parseConfiguration(file, content, parsers);
|
|
1015
1085
|
if (!config) {
|
|
@@ -1019,9 +1089,9 @@ function readConfigSync(file, parsers) {
|
|
|
1019
1089
|
const configExtends = config.extends;
|
|
1020
1090
|
if (configExtends) {
|
|
1021
1091
|
delete config.extends;
|
|
1022
|
-
const resolvedExtends =
|
|
1092
|
+
const resolvedExtends = resolveConfigExtendsSync(file, configExtends, fs);
|
|
1023
1093
|
return {
|
|
1024
|
-
...readConfigSync(resolvedExtends, parsers),
|
|
1094
|
+
...readConfigSync(resolvedExtends, parsers, fs),
|
|
1025
1095
|
...config
|
|
1026
1096
|
};
|
|
1027
1097
|
}
|
|
@@ -1034,7 +1104,7 @@ function readConfigSync(file, parsers) {
|
|
|
1034
1104
|
* @returns {string} SemVer string.
|
|
1035
1105
|
*/
|
|
1036
1106
|
function getVersion() {
|
|
1037
|
-
return require("
|
|
1107
|
+
return require("./constants").version;
|
|
1038
1108
|
}
|
|
1039
1109
|
|
|
1040
1110
|
// Export a/synchronous/Promise APIs
|
|
@@ -1104,7 +1174,7 @@ module.exports = markdownlint;
|
|
|
1104
1174
|
*
|
|
1105
1175
|
* @typedef {Object} RuleOnErrorInfo
|
|
1106
1176
|
* @property {number} lineNumber Line number (1-based).
|
|
1107
|
-
* @property {string} [
|
|
1177
|
+
* @property {string} [detail] Detail about the error.
|
|
1108
1178
|
* @property {string} [context] Context for the error.
|
|
1109
1179
|
* @property {number[]} [range] Column number (1-based) and length.
|
|
1110
1180
|
* @property {RuleOnErrorFixInfo} [fixInfo] Fix information.
|
|
@@ -1128,6 +1198,7 @@ module.exports = markdownlint;
|
|
|
1128
1198
|
* @property {string} description Rule description.
|
|
1129
1199
|
* @property {URL} [information] Link to more information.
|
|
1130
1200
|
* @property {string[]} tags Rule tag(s).
|
|
1201
|
+
* @property {boolean} [asynchronous] True if asynchronous.
|
|
1131
1202
|
* @property {RuleFunction} function Rule implementation.
|
|
1132
1203
|
*/
|
|
1133
1204
|
|
|
@@ -1144,6 +1215,7 @@ module.exports = markdownlint;
|
|
|
1144
1215
|
* @property {boolean} [noInlineConfig] True to ignore HTML directives.
|
|
1145
1216
|
* @property {number} [resultVersion] Results object version.
|
|
1146
1217
|
* @property {Plugin[]} [markdownItPlugins] Additional plugins.
|
|
1218
|
+
* @property {Object} [fs] File system implementation.
|
|
1147
1219
|
*/
|
|
1148
1220
|
|
|
1149
1221
|
/**
|
|
@@ -1178,20 +1250,21 @@ module.exports = markdownlint;
|
|
|
1178
1250
|
* @property {string} errorDetail Detail about the error.
|
|
1179
1251
|
* @property {string} errorContext Context for the error.
|
|
1180
1252
|
* @property {number[]} errorRange Column number (1-based) and length.
|
|
1181
|
-
* @property {FixInfo} fixInfo Fix information.
|
|
1253
|
+
* @property {FixInfo} [fixInfo] Fix information.
|
|
1182
1254
|
*/
|
|
1183
1255
|
|
|
1184
1256
|
/**
|
|
1185
1257
|
* Fix information.
|
|
1186
1258
|
*
|
|
1187
1259
|
* @typedef {Object} FixInfo
|
|
1260
|
+
* @property {number} [lineNumber] Line number (1-based).
|
|
1188
1261
|
* @property {number} [editColumn] Column of the fix (1-based).
|
|
1189
1262
|
* @property {number} [deleteCount] Count of characters to delete.
|
|
1190
1263
|
* @property {string} [insertText] Text to insert (after deleting).
|
|
1191
1264
|
*/
|
|
1192
1265
|
|
|
1193
1266
|
/**
|
|
1194
|
-
* Called with the result of the lint
|
|
1267
|
+
* Called with the result of the lint function.
|
|
1195
1268
|
*
|
|
1196
1269
|
* @callback LintCallback
|
|
1197
1270
|
* @param {Error | null} err Error object or null.
|
|
@@ -1221,10 +1294,19 @@ module.exports = markdownlint;
|
|
|
1221
1294
|
*/
|
|
1222
1295
|
|
|
1223
1296
|
/**
|
|
1224
|
-
* Called with the result of the readConfig
|
|
1297
|
+
* Called with the result of the readConfig function.
|
|
1225
1298
|
*
|
|
1226
1299
|
* @callback ReadConfigCallback
|
|
1227
1300
|
* @param {Error | null} err Error object or null.
|
|
1228
1301
|
* @param {Configuration} [config] Configuration object.
|
|
1229
1302
|
* @returns {void}
|
|
1230
1303
|
*/
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Called with the result of the resolveConfigExtends function.
|
|
1307
|
+
*
|
|
1308
|
+
* @callback ResolveConfigExtendsCallback
|
|
1309
|
+
* @param {Error | null} err Error object or null.
|
|
1310
|
+
* @param {string} [path] Resolved path to file.
|
|
1311
|
+
* @returns {void}
|
|
1312
|
+
*/
|