diff --git a/helpers/helpers.js b/helpers/helpers.js index d86a20d3..87c49acf 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -80,7 +80,7 @@ module.exports.includesSorted = function includesSorted(array, element) { let left = 0; let right = array.length - 1; while (left <= right) { - /* eslint-disable no-bitwise */ + // eslint-disable-next-line no-bitwise const mid = (left + right) >> 1; if (array[mid] < element) { left = mid + 1; diff --git a/lib/markdownlint.js b/lib/markdownlint.js index c6849f85..0029c9d5 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -90,7 +90,9 @@ function newResults(ruleList) { function toString(useAlias) { let ruleNameToRule = null; 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]; if (Array.isArray(fileResults)) { fileResults.forEach(function forResult(result) { @@ -727,53 +729,98 @@ function lintInput(options, synchronous, callback) { md.use(...plugin); }); const results = newResults(ruleList); - // Helper to lint the next string or file - /* eslint-disable consistent-return */ + let done = false; + // Linting of strings is always synchronous + let syncItem = null; // eslint-disable-next-line jsdoc/require-jsdoc - function lintNextItem() { - let iterating = true; - let item = null; - // eslint-disable-next-line jsdoc/require-jsdoc - 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); - } + function syncCallback(err, result) { + if (err) { + done = true; + return callback(err); } + 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; } /** diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index af14d431..3598159d 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -440,9 +440,6 @@ tape("resultFormattingV1", (test) => { test.deepEqual(actualResult, expectedResult, "Undetected issues."); const actualMessage = actualResult.toString(); 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" + " First heading should be a top level heading" + " [Expected: h1; Actual: h2]\n" + @@ -457,7 +454,10 @@ tape("resultFormattingV1", (test) => { " [Context: \"## Heading 3 {MD019}\"]\n" + "./test/first_heading_bad_atx.md: 1: MD002/first-heading-h1" + " 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.end(); }); @@ -535,9 +535,6 @@ tape("resultFormattingV2", (test) => { test.deepEqual(actualResult, expectedResult, "Undetected issues."); const actualMessage = actualResult.toString(); 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/first-header-h1" + " First heading should be a top level heading" + @@ -554,7 +551,10 @@ tape("resultFormattingV2", (test) => { "./test/first_heading_bad_atx.md: 1:" + " MD002/first-heading-h1/first-header-h1" + " 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.end(); });