Allow custom file system implementation to be passed when linting or reading configuration.

This commit is contained in:
David Anson 2021-08-12 19:38:03 -07:00
parent b10147f16b
commit 211f09afbc
6 changed files with 277 additions and 52 deletions

View file

@ -2,7 +2,6 @@
"use strict";
const fs = require("fs");
const path = require("path");
const { promisify } = require("util");
const markdownIt = require("markdown-it");
@ -685,6 +684,7 @@ function lintContent(
* @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
* @param {boolean} noInlineConfig Whether to allow inline configuration.
* @param {number} resultVersion Version of the LintResults object to return.
* @param {Object} fs File system implementation.
* @param {boolean} synchronous Whether to execute synchronously.
* @param {Function} callback Callback (err, result) function.
* @returns {void}
@ -698,6 +698,7 @@ function lintFile(
handleRuleFailures,
noInlineConfig,
resultVersion,
fs,
synchronous,
callback) {
// eslint-disable-next-line jsdoc/require-jsdoc
@ -710,7 +711,6 @@ function lintFile(
}
// Make a/synchronous call to read file
if (synchronous) {
// @ts-ignore
lintContentWrapper(null, fs.readFileSync(file, "utf8"));
} else {
fs.readFile(file, "utf8", lintContentWrapper);
@ -756,6 +756,7 @@ function lintInput(options, synchronous, callback) {
// @ts-ignore
md.use(...plugin);
});
const fs = options.fs || require("fs");
const results = newResults(ruleList);
let done = false;
// Linting of strings is always synchronous
@ -795,6 +796,7 @@ function lintInput(options, synchronous, callback) {
handleRuleFailures,
noInlineConfig,
resultVersion,
fs,
synchronous,
syncCallback
);
@ -819,6 +821,7 @@ function lintInput(options, synchronous, callback) {
handleRuleFailures,
noInlineConfig,
resultVersion,
fs,
synchronous,
(err, result) => {
concurrency--;
@ -929,17 +932,17 @@ function parseConfiguration(name, content, parsers) {
*
* @param {string} configFile Configuration file name.
* @param {string} referenceId Referenced identifier to resolve.
* @param {Object} fs File system implementation.
* @returns {string} Resolved path to file.
*/
function resolveConfigExtends(configFile, referenceId) {
function resolveConfigExtends(configFile, referenceId, fs) {
const configFileDirname = path.dirname(configFile);
const resolvedExtendsFile = path.resolve(configFileDirname, referenceId);
try {
if (fs.statSync(resolvedExtendsFile).isFile()) {
return resolvedExtendsFile;
}
fs.accessSync(resolvedExtendsFile);
return resolvedExtendsFile;
} catch {
// If not a file or fs.statSync throws, try require.resolve
// Not a file, try require.resolve
}
try {
return dynamicRequire.resolve(
@ -947,7 +950,7 @@ function resolveConfigExtends(configFile, referenceId) {
{ "paths": [ configFileDirname ] }
);
} catch {
// If require.resolve throws, return resolvedExtendsFile
// Unable to resolve, return resolvedExtendsFile
}
return resolvedExtendsFile;
}
@ -958,14 +961,23 @@ function resolveConfigExtends(configFile, referenceId) {
* @param {string} file Configuration file name.
* @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing
* function(s).
* @param {Object} [fs] File system implementation.
* @param {ReadConfigCallback} [callback] Callback (err, result) function.
* @returns {void}
*/
function readConfig(file, parsers, callback) {
function readConfig(file, parsers, fs, callback) {
if (!callback) {
// @ts-ignore
callback = parsers;
parsers = null;
if (fs) {
callback = fs;
fs = null;
} else {
// @ts-ignore
callback = parsers;
parsers = null;
}
}
if (!fs) {
fs = require("fs");
}
// Read file
fs.readFile(file, "utf8", (err, content) => {
@ -982,8 +994,8 @@ function readConfig(file, parsers, callback) {
const configExtends = config.extends;
if (configExtends) {
delete config.extends;
const resolvedExtends = resolveConfigExtends(file, configExtends);
return readConfig(resolvedExtends, parsers, (errr, extendsConfig) => {
const resolvedExtends = resolveConfigExtends(file, configExtends, fs);
return readConfig(resolvedExtends, parsers, fs, (errr, extendsConfig) => {
if (errr) {
return callback(errr);
}
@ -1004,11 +1016,12 @@ const readConfigPromisify = promisify && promisify(readConfig);
*
* @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation.
* @returns {Promise<Configuration>} Configuration object.
*/
function readConfigPromise(file, parsers) {
function readConfigPromise(file, parsers, fs) {
// @ts-ignore
return readConfigPromisify(file, parsers);
return readConfigPromisify(file, parsers, fs);
}
/**
@ -1016,11 +1029,14 @@ function readConfigPromise(file, parsers) {
*
* @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation.
* @returns {Configuration} Configuration object.
*/
function readConfigSync(file, parsers) {
function readConfigSync(file, parsers, fs) {
if (!fs) {
fs = require("fs");
}
// Read file
// @ts-ignore
const content = fs.readFileSync(file, "utf8");
// Try to parse file
const { config, message } = parseConfiguration(file, content, parsers);
@ -1031,9 +1047,9 @@ function readConfigSync(file, parsers) {
const configExtends = config.extends;
if (configExtends) {
delete config.extends;
const resolvedExtends = resolveConfigExtends(file, configExtends);
const resolvedExtends = resolveConfigExtends(file, configExtends, fs);
return {
...readConfigSync(resolvedExtends, parsers),
...readConfigSync(resolvedExtends, parsers, fs),
...config
};
}
@ -1156,6 +1172,7 @@ module.exports = markdownlint;
* @property {boolean} [noInlineConfig] True to ignore HTML directives.
* @property {number} [resultVersion] Results object version.
* @property {Plugin[]} [markdownItPlugins] Additional plugins.
* @property {Object} [fs] File system implementation.
*/
/**