mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-09-22 05:40:48 +02:00
Introduce concurrency to asynchronous file linting for improved performance.
This commit is contained in:
parent
2851a691ba
commit
4286f68152
3 changed files with 101 additions and 54 deletions
|
@ -80,7 +80,7 @@ module.exports.includesSorted = function includesSorted(array, element) {
|
||||||
let left = 0;
|
let left = 0;
|
||||||
let right = array.length - 1;
|
let right = array.length - 1;
|
||||||
while (left <= right) {
|
while (left <= right) {
|
||||||
/* eslint-disable no-bitwise */
|
// eslint-disable-next-line no-bitwise
|
||||||
const mid = (left + right) >> 1;
|
const mid = (left + right) >> 1;
|
||||||
if (array[mid] < element) {
|
if (array[mid] < element) {
|
||||||
left = mid + 1;
|
left = mid + 1;
|
||||||
|
|
|
@ -90,7 +90,9 @@ function newResults(ruleList) {
|
||||||
function toString(useAlias) {
|
function toString(useAlias) {
|
||||||
let ruleNameToRule = null;
|
let ruleNameToRule = null;
|
||||||
const results = [];
|
const results = [];
|
||||||
Object.keys(lintResults).forEach(function forFile(file) {
|
const keys = Object.keys(lintResults);
|
||||||
|
keys.sort();
|
||||||
|
keys.forEach(function forFile(file) {
|
||||||
const fileResults = lintResults[file];
|
const fileResults = lintResults[file];
|
||||||
if (Array.isArray(fileResults)) {
|
if (Array.isArray(fileResults)) {
|
||||||
fileResults.forEach(function forResult(result) {
|
fileResults.forEach(function forResult(result) {
|
||||||
|
@ -727,53 +729,98 @@ function lintInput(options, synchronous, callback) {
|
||||||
md.use(...plugin);
|
md.use(...plugin);
|
||||||
});
|
});
|
||||||
const results = newResults(ruleList);
|
const results = newResults(ruleList);
|
||||||
// Helper to lint the next string or file
|
let done = false;
|
||||||
/* eslint-disable consistent-return */
|
// Linting of strings is always synchronous
|
||||||
|
let syncItem = null;
|
||||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||||
function lintNextItem() {
|
function syncCallback(err, result) {
|
||||||
let iterating = true;
|
if (err) {
|
||||||
let item = null;
|
done = true;
|
||||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
return callback(err);
|
||||||
function lintNextItemCallback(err, result) {
|
|
||||||
if (err) {
|
|
||||||
iterating = false;
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
results[item] = result;
|
|
||||||
return iterating || lintNextItem();
|
|
||||||
}
|
|
||||||
while (iterating) {
|
|
||||||
if ((item = stringsKeys.shift())) {
|
|
||||||
lintContent(
|
|
||||||
ruleList,
|
|
||||||
item,
|
|
||||||
strings[item] || "",
|
|
||||||
md,
|
|
||||||
config,
|
|
||||||
frontMatter,
|
|
||||||
handleRuleFailures,
|
|
||||||
noInlineConfig,
|
|
||||||
resultVersion,
|
|
||||||
lintNextItemCallback);
|
|
||||||
} else if ((item = files.shift())) {
|
|
||||||
iterating = synchronous;
|
|
||||||
lintFile(
|
|
||||||
ruleList,
|
|
||||||
item,
|
|
||||||
md,
|
|
||||||
config,
|
|
||||||
frontMatter,
|
|
||||||
handleRuleFailures,
|
|
||||||
noInlineConfig,
|
|
||||||
resultVersion,
|
|
||||||
synchronous,
|
|
||||||
lintNextItemCallback);
|
|
||||||
} else {
|
|
||||||
return callback(null, results);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
results[syncItem] = result;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return lintNextItem();
|
while (!done && (syncItem = stringsKeys.shift())) {
|
||||||
|
lintContent(
|
||||||
|
ruleList,
|
||||||
|
syncItem,
|
||||||
|
strings[syncItem] || "",
|
||||||
|
md,
|
||||||
|
config,
|
||||||
|
frontMatter,
|
||||||
|
handleRuleFailures,
|
||||||
|
noInlineConfig,
|
||||||
|
resultVersion,
|
||||||
|
syncCallback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (synchronous) {
|
||||||
|
// Lint files synchronously
|
||||||
|
while (!done && (syncItem = files.shift())) {
|
||||||
|
lintFile(
|
||||||
|
ruleList,
|
||||||
|
syncItem,
|
||||||
|
md,
|
||||||
|
config,
|
||||||
|
frontMatter,
|
||||||
|
handleRuleFailures,
|
||||||
|
noInlineConfig,
|
||||||
|
resultVersion,
|
||||||
|
synchronous,
|
||||||
|
syncCallback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return done || callback(null, results);
|
||||||
|
}
|
||||||
|
// Lint files asynchronously
|
||||||
|
let concurrency = 0;
|
||||||
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||||
|
function lintConcurrently() {
|
||||||
|
const asyncItem = files.shift();
|
||||||
|
if (done) {
|
||||||
|
// Nothing to do
|
||||||
|
} else if (asyncItem) {
|
||||||
|
concurrency++;
|
||||||
|
lintFile(
|
||||||
|
ruleList,
|
||||||
|
asyncItem,
|
||||||
|
md,
|
||||||
|
config,
|
||||||
|
frontMatter,
|
||||||
|
handleRuleFailures,
|
||||||
|
noInlineConfig,
|
||||||
|
resultVersion,
|
||||||
|
synchronous,
|
||||||
|
(err, result) => {
|
||||||
|
concurrency--;
|
||||||
|
if (err) {
|
||||||
|
done = true;
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
results[asyncItem] = result;
|
||||||
|
lintConcurrently();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (concurrency === 0) {
|
||||||
|
done = true;
|
||||||
|
return callback(null, results);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access
|
||||||
|
// delay suggests that a concurrency factor of 8 can eliminate the impact
|
||||||
|
// of that delay (i.e., total time is the same as with no delay).
|
||||||
|
lintConcurrently();
|
||||||
|
lintConcurrently();
|
||||||
|
lintConcurrently();
|
||||||
|
lintConcurrently();
|
||||||
|
lintConcurrently();
|
||||||
|
lintConcurrently();
|
||||||
|
lintConcurrently();
|
||||||
|
lintConcurrently();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -440,9 +440,6 @@ tape("resultFormattingV1", (test) => {
|
||||||
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||||
const actualMessage = actualResult.toString();
|
const actualMessage = actualResult.toString();
|
||||||
const expectedMessage =
|
const expectedMessage =
|
||||||
"truncate: 1: MD021/no-multiple-space-closed-atx" +
|
|
||||||
" Multiple spaces inside hashes on closed atx style heading" +
|
|
||||||
" [Context: \"# Multiple spa...tyle heading #\"]\n" +
|
|
||||||
"./test/atx_heading_spacing.md: 3: MD002/first-heading-h1" +
|
"./test/atx_heading_spacing.md: 3: MD002/first-heading-h1" +
|
||||||
" First heading should be a top level heading" +
|
" First heading should be a top level heading" +
|
||||||
" [Expected: h1; Actual: h2]\n" +
|
" [Expected: h1; Actual: h2]\n" +
|
||||||
|
@ -457,7 +454,10 @@ tape("resultFormattingV1", (test) => {
|
||||||
" [Context: \"## Heading 3 {MD019}\"]\n" +
|
" [Context: \"## Heading 3 {MD019}\"]\n" +
|
||||||
"./test/first_heading_bad_atx.md: 1: MD002/first-heading-h1" +
|
"./test/first_heading_bad_atx.md: 1: MD002/first-heading-h1" +
|
||||||
" First heading should be a top level heading" +
|
" First heading should be a top level heading" +
|
||||||
" [Expected: h1; Actual: h2]";
|
" [Expected: h1; Actual: h2]\n" +
|
||||||
|
"truncate: 1: MD021/no-multiple-space-closed-atx" +
|
||||||
|
" Multiple spaces inside hashes on closed atx style heading" +
|
||||||
|
" [Context: \"# Multiple spa...tyle heading #\"]";
|
||||||
test.equal(actualMessage, expectedMessage, "Incorrect message.");
|
test.equal(actualMessage, expectedMessage, "Incorrect message.");
|
||||||
test.end();
|
test.end();
|
||||||
});
|
});
|
||||||
|
@ -535,9 +535,6 @@ tape("resultFormattingV2", (test) => {
|
||||||
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||||
const actualMessage = actualResult.toString();
|
const actualMessage = actualResult.toString();
|
||||||
const expectedMessage =
|
const expectedMessage =
|
||||||
"truncate: 1: MD021/no-multiple-space-closed-atx" +
|
|
||||||
" Multiple spaces inside hashes on closed atx style heading" +
|
|
||||||
" [Context: \"# Multiple spa...tyle heading #\"]\n" +
|
|
||||||
"./test/atx_heading_spacing.md: 3:" +
|
"./test/atx_heading_spacing.md: 3:" +
|
||||||
" MD002/first-heading-h1/first-header-h1" +
|
" MD002/first-heading-h1/first-header-h1" +
|
||||||
" First heading should be a top level heading" +
|
" First heading should be a top level heading" +
|
||||||
|
@ -554,7 +551,10 @@ tape("resultFormattingV2", (test) => {
|
||||||
"./test/first_heading_bad_atx.md: 1:" +
|
"./test/first_heading_bad_atx.md: 1:" +
|
||||||
" MD002/first-heading-h1/first-header-h1" +
|
" MD002/first-heading-h1/first-header-h1" +
|
||||||
" First heading should be a top level heading" +
|
" First heading should be a top level heading" +
|
||||||
" [Expected: h1; Actual: h2]";
|
" [Expected: h1; Actual: h2]\n" +
|
||||||
|
"truncate: 1: MD021/no-multiple-space-closed-atx" +
|
||||||
|
" Multiple spaces inside hashes on closed atx style heading" +
|
||||||
|
" [Context: \"# Multiple spa...tyle heading #\"]";
|
||||||
test.equal(actualMessage, expectedMessage, "Incorrect message.");
|
test.equal(actualMessage, expectedMessage, "Incorrect message.");
|
||||||
test.end();
|
test.end();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue