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

@ -39,7 +39,7 @@
"max-depth": "off", "max-depth": "off",
"max-lines": "off", "max-lines": "off",
"max-lines-per-function": "off", "max-lines-per-function": "off",
"max-params": ["error", 10], "max-params": ["off"],
"max-statements": "off", "max-statements": "off",
"multiline-comment-style": ["error", "separate-lines"], "multiline-comment-style": ["error", "separate-lines"],
"multiline-ternary": "off", "multiline-ternary": "off",

View file

@ -294,7 +294,8 @@ function markdownlint(options) { ... }
Type: `Object` Type: `Object`
Configures the function. Configures the function. All properties are optional, but at least one
of `files` or `strings` should be set to provide input.
##### options.customRules ##### options.customRules
@ -534,6 +535,16 @@ Each item in the top-level `Array` should be of the form:
[ require("markdown-it-plugin"), plugin_param_0, plugin_param_1, ... ] [ require("markdown-it-plugin"), plugin_param_0, plugin_param_1, ... ]
``` ```
##### options.fs
Type: `Object` implementing the [file system API](https://nodejs.org/api/fs.html)
In advanced scenarios, it may be desirable to bypass the default file system API.
If a custom file system implementation is provided, `markdownlint` will use that
instead of invoking `require("fs")`.
Note: The only methods called are `readFile` and `readFileSync`.
#### callback #### callback
Type: `Function` taking (`Error`, `Object`) Type: `Function` taking (`Error`, `Object`)
@ -566,10 +577,11 @@ Asynchronous API:
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing function(s). * @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing function(s).
* @param {Object} [fs] File system implementation.
* @param {ReadConfigCallback} [callback] Callback (err, result) function. * @param {ReadConfigCallback} [callback] Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
function readConfig(file, parsers, callback) { ... } function readConfig(file, parsers, fs, callback) { ... }
``` ```
Synchronous API: Synchronous API:
@ -580,13 +592,14 @@ Synchronous API:
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation.
* @returns {Configuration} Configuration object. * @returns {Configuration} Configuration object.
*/ */
function readConfigSync(file, parsers) { ... } function readConfigSync(file, parsers, fs) { ... }
``` ```
Promise API (in the `promises` namespace like Node.js's Promise API (in the `promises` namespace like Node.js's
[`fs` Promises API](https://nodejs.org/api/fs.html#fs_fs_promises_api)): [`fs` Promises API](https://nodejs.org/api/fs.html#fs_promises_api)):
```js ```js
/** /**
@ -594,9 +607,10 @@ Promise API (in the `promises` namespace like Node.js's
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation.
* @returns {Promise<Configuration>} Configuration object. * @returns {Promise<Configuration>} Configuration object.
*/ */
function readConfig(file, parsers) { ... } function readConfig(file, parsers, fs) { ... }
``` ```
#### file #### file
@ -627,6 +641,16 @@ For example:
[ JSON.parse, require("toml").parse, require("js-yaml").load ] [ JSON.parse, require("toml").parse, require("js-yaml").load ]
``` ```
#### fs
Type: *Optional* `Object` implementing the [file system API](https://nodejs.org/api/fs.html)
In advanced scenarios, it may be desirable to bypass the default file system API.
If a custom file system implementation is provided, `markdownlint` will use that
instead of invoking `require("fs")`.
Note: The only methods called are `readFile`, `readFileSync`, and `accessSync`.
#### callback #### callback
Type: `Function` taking (`Error`, `Object`) Type: `Function` taking (`Error`, `Object`)

View file

@ -813,7 +813,6 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from) {
to[j] = from[i]; to[j] = from[i];
return to; return to;
}; };
var fs = __webpack_require__(/*! fs */ "?ec0a");
var path = __webpack_require__(/*! path */ "?b85c"); var path = __webpack_require__(/*! path */ "?b85c");
var promisify = __webpack_require__(/*! util */ "?96a2").promisify; var promisify = __webpack_require__(/*! util */ "?96a2").promisify;
var markdownIt = __webpack_require__(/*! markdown-it */ "markdown-it"); var markdownIt = __webpack_require__(/*! markdown-it */ "markdown-it");
@ -1446,11 +1445,12 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul
* @param {boolean} handleRuleFailures Whether to handle exceptions in rules. * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
* @param {boolean} noInlineConfig Whether to allow inline configuration. * @param {boolean} noInlineConfig Whether to allow inline configuration.
* @param {number} resultVersion Version of the LintResults object to return. * @param {number} resultVersion Version of the LintResults object to return.
* @param {Object} fs File system implementation.
* @param {boolean} synchronous Whether to execute synchronously. * @param {boolean} synchronous Whether to execute synchronously.
* @param {Function} callback Callback (err, result) function. * @param {Function} callback Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
function lintFile(ruleList, file, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, synchronous, callback) { function lintFile(ruleList, file, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, callback) {
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function lintContentWrapper(err, content) { function lintContentWrapper(err, content) {
if (err) { if (err) {
@ -1460,7 +1460,6 @@ function lintFile(ruleList, file, md, config, frontMatter, handleRuleFailures, n
} }
// Make a/synchronous call to read file // Make a/synchronous call to read file
if (synchronous) { if (synchronous) {
// @ts-ignore
lintContentWrapper(null, fs.readFileSync(file, "utf8")); lintContentWrapper(null, fs.readFileSync(file, "utf8"));
} }
else { else {
@ -1507,6 +1506,7 @@ function lintInput(options, synchronous, callback) {
// @ts-ignore // @ts-ignore
md.use.apply(md, plugin); md.use.apply(md, plugin);
}); });
var fs = options.fs || __webpack_require__(/*! fs */ "?ec0a");
var results = newResults(ruleList); var results = newResults(ruleList);
var done = false; var done = false;
// Linting of strings is always synchronous // Linting of strings is always synchronous
@ -1526,7 +1526,7 @@ function lintInput(options, synchronous, callback) {
if (synchronous) { if (synchronous) {
// Lint files synchronously // Lint files synchronously
while (!done && (syncItem = files.shift())) { while (!done && (syncItem = files.shift())) {
lintFile(ruleList, syncItem, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, synchronous, syncCallback); lintFile(ruleList, syncItem, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, syncCallback);
} }
return done || callback(null, results); return done || callback(null, results);
} }
@ -1540,7 +1540,7 @@ function lintInput(options, synchronous, callback) {
} }
else if (asyncItem) { else if (asyncItem) {
concurrency++; concurrency++;
lintFile(ruleList, asyncItem, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, synchronous, function (err, result) { lintFile(ruleList, asyncItem, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, function (err, result) {
concurrency--; concurrency--;
if (err) { if (err) {
done = true; done = true;
@ -1644,24 +1644,24 @@ function parseConfiguration(name, content, parsers) {
* *
* @param {string} configFile Configuration file name. * @param {string} configFile Configuration file name.
* @param {string} referenceId Referenced identifier to resolve. * @param {string} referenceId Referenced identifier to resolve.
* @param {Object} fs File system implementation.
* @returns {string} Resolved path to file. * @returns {string} Resolved path to file.
*/ */
function resolveConfigExtends(configFile, referenceId) { function resolveConfigExtends(configFile, referenceId, fs) {
var configFileDirname = path.dirname(configFile); var configFileDirname = path.dirname(configFile);
var resolvedExtendsFile = path.resolve(configFileDirname, referenceId); var resolvedExtendsFile = path.resolve(configFileDirname, referenceId);
try { try {
if (fs.statSync(resolvedExtendsFile).isFile()) { fs.accessSync(resolvedExtendsFile);
return resolvedExtendsFile; return resolvedExtendsFile;
}
} }
catch (_a) { catch (_a) {
// If not a file or fs.statSync throws, try require.resolve // Not a file, try require.resolve
} }
try { try {
return dynamicRequire.resolve(referenceId, { "paths": [configFileDirname] }); return dynamicRequire.resolve(referenceId, { "paths": [configFileDirname] });
} }
catch (_b) { catch (_b) {
// If require.resolve throws, return resolvedExtendsFile // Unable to resolve, return resolvedExtendsFile
} }
return resolvedExtendsFile; return resolvedExtendsFile;
} }
@ -1671,14 +1671,24 @@ function resolveConfigExtends(configFile, referenceId) {
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing * @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing
* function(s). * function(s).
* @param {Object} [fs] File system implementation.
* @param {ReadConfigCallback} [callback] Callback (err, result) function. * @param {ReadConfigCallback} [callback] Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
function readConfig(file, parsers, callback) { function readConfig(file, parsers, fs, callback) {
if (!callback) { if (!callback) {
// @ts-ignore if (fs) {
callback = parsers; callback = fs;
parsers = null; fs = null;
}
else {
// @ts-ignore
callback = parsers;
parsers = null;
}
}
if (!fs) {
fs = __webpack_require__(/*! fs */ "?ec0a");
} }
// Read file // Read file
fs.readFile(file, "utf8", function (err, content) { fs.readFile(file, "utf8", function (err, content) {
@ -1695,8 +1705,8 @@ function readConfig(file, parsers, callback) {
var configExtends = config["extends"]; var configExtends = config["extends"];
if (configExtends) { if (configExtends) {
delete config["extends"]; delete config["extends"];
var resolvedExtends = resolveConfigExtends(file, configExtends); var resolvedExtends = resolveConfigExtends(file, configExtends, fs);
return readConfig(resolvedExtends, parsers, function (errr, extendsConfig) { return readConfig(resolvedExtends, parsers, fs, function (errr, extendsConfig) {
if (errr) { if (errr) {
return callback(errr); return callback(errr);
} }
@ -1712,22 +1722,26 @@ var readConfigPromisify = promisify && promisify(readConfig);
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation.
* @returns {Promise<Configuration>} Configuration object. * @returns {Promise<Configuration>} Configuration object.
*/ */
function readConfigPromise(file, parsers) { function readConfigPromise(file, parsers, fs) {
// @ts-ignore // @ts-ignore
return readConfigPromisify(file, parsers); return readConfigPromisify(file, parsers, fs);
} }
/** /**
* Read specified configuration file synchronously. * Read specified configuration file synchronously.
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation.
* @returns {Configuration} Configuration object. * @returns {Configuration} Configuration object.
*/ */
function readConfigSync(file, parsers) { function readConfigSync(file, parsers, fs) {
if (!fs) {
fs = __webpack_require__(/*! fs */ "?ec0a");
}
// Read file // Read file
// @ts-ignore
var content = fs.readFileSync(file, "utf8"); var content = fs.readFileSync(file, "utf8");
// Try to parse file // Try to parse file
var _a = parseConfiguration(file, content, parsers), config = _a.config, message = _a.message; var _a = parseConfiguration(file, content, parsers), config = _a.config, message = _a.message;
@ -1738,8 +1752,8 @@ function readConfigSync(file, parsers) {
var configExtends = config["extends"]; var configExtends = config["extends"];
if (configExtends) { if (configExtends) {
delete config["extends"]; delete config["extends"];
var resolvedExtends = resolveConfigExtends(file, configExtends); var resolvedExtends = resolveConfigExtends(file, configExtends, fs);
return __assign(__assign({}, readConfigSync(resolvedExtends, parsers)), config); return __assign(__assign({}, readConfigSync(resolvedExtends, parsers, fs)), config);
} }
return config; return config;
} }

13
lib/markdownlint.d.ts vendored
View file

@ -52,6 +52,10 @@ type Options = {
* Additional plugins. * Additional plugins.
*/ */
markdownItPlugins?: Plugin[]; markdownItPlugins?: Plugin[];
/**
* File system implementation.
*/
fs?: any;
}; };
/** /**
* Called with the result of the lint operation. * Called with the result of the lint operation.
@ -70,18 +74,20 @@ declare function markdownlintSync(options: Options): LintResults;
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing * @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing
* function(s). * function(s).
* @param {Object} [fs] File system implementation.
* @param {ReadConfigCallback} [callback] Callback (err, result) function. * @param {ReadConfigCallback} [callback] Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
declare function readConfig(file: string, parsers: ConfigurationParser[] | ReadConfigCallback, callback?: ReadConfigCallback): void; declare function readConfig(file: string, parsers: ConfigurationParser[] | ReadConfigCallback, fs?: any, callback?: ReadConfigCallback): void;
/** /**
* Read specified configuration file synchronously. * Read specified configuration file synchronously.
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation.
* @returns {Configuration} Configuration object. * @returns {Configuration} Configuration object.
*/ */
declare function readConfigSync(file: string, parsers?: ConfigurationParser[]): Configuration; declare function readConfigSync(file: string, parsers?: ConfigurationParser[], fs?: any): Configuration;
/** /**
* Gets the (semantic) version of the library. * Gets the (semantic) version of the library.
* *
@ -364,6 +370,7 @@ declare function markdownlintPromise(options: Options): Promise<LintResults>;
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation.
* @returns {Promise<Configuration>} Configuration object. * @returns {Promise<Configuration>} Configuration object.
*/ */
declare function readConfigPromise(file: string, parsers?: ConfigurationParser[]): Promise<Configuration>; declare function readConfigPromise(file: string, parsers?: ConfigurationParser[], fs?: any): Promise<Configuration>;

View file

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

View file

@ -652,11 +652,13 @@ test.cb("readmeHeadings", (t) => {
"##### options.noInlineConfig", "##### options.noInlineConfig",
"##### options.resultVersion", "##### options.resultVersion",
"##### options.markdownItPlugins", "##### options.markdownItPlugins",
"##### options.fs",
"#### callback", "#### callback",
"#### result", "#### result",
"### Config", "### Config",
"#### file", "#### file",
"#### parsers", "#### parsers",
"#### fs",
"#### callback", "#### callback",
"#### result", "#### result",
"## Usage", "## Usage",
@ -798,6 +800,40 @@ test.cb("missingStringValue", (t) => {
}); });
}); });
test("customFileSystemSync", (t) => {
t.plan(2);
const file = "/dir/file.md";
const fsApi = {
"readFileSync": (p) => {
t.is(p, file);
return "# Heading";
}
};
const result = markdownlint.sync({
"files": file,
"fs": fsApi
});
t.deepEqual(result[file].length, 1, "Did not report violations.");
});
test.cb("customFileSystemAsync", (t) => {
t.plan(3);
const file = "/dir/file.md";
const fsApi = {
"readFile": (p, o, cb) => {
t.is(p, file);
cb(null, "# Heading");
}
};
markdownlint({
"files": file,
"fs": fsApi
}, function callback(err, result) {
t.falsy(err);
t.deepEqual(result[file].length, 1, "Did not report violations.");
t.end();
});
});
test.cb("readme", (t) => { test.cb("readme", (t) => {
t.plan(115); t.plan(115);
const tagToRules = {}; const tagToRules = {};
@ -1105,6 +1141,52 @@ test.cb("configMultipleWithRequireResolve", (t) => {
}); });
}); });
test.cb("configCustomFileSystem", (t) => {
t.plan(5);
const file = "/dir/file.json";
const extended = "/dir/extended.json";
const fileContent = {
"extends": extended,
"default": true,
"MD001": false
};
const extendedContent = {
"MD001": true,
"MD002": true
};
const fsApi = {
"accessSync": (p) => {
t.is(p, extended);
},
"readFile": (p, o, cb) => {
switch (p) {
case file:
t.is(p, file);
return cb(null, JSON.stringify(fileContent));
case extended:
t.is(p, extended);
return cb(null, JSON.stringify(extendedContent));
default:
return t.fail();
}
}
};
markdownlint.readConfig(
file,
null,
fsApi,
function callback(err, actual) {
t.falsy(err);
const expected = {
...extendedContent,
...fileContent
};
delete expected.extends;
t.deepEqual(actual, expected, "Config object not correct.");
t.end();
});
});
test.cb("configBadFile", (t) => { test.cb("configBadFile", (t) => {
t.plan(4); t.plan(4);
markdownlint.readConfig("./test/config/config-badfile.json", markdownlint.readConfig("./test/config/config-badfile.json",
@ -1358,6 +1440,45 @@ test("configMultipleHybridSync", (t) => {
t.like(actual, expected, "Config object not correct."); t.like(actual, expected, "Config object not correct.");
}); });
test("configCustomFileSystemSync", (t) => {
t.plan(4);
const file = "/dir/file.json";
const extended = "/dir/extended.json";
const fileContent = {
"extends": extended,
"default": true,
"MD001": false
};
const extendedContent = {
"MD001": true,
"MD002": true
};
const fsApi = {
"accessSync": (p) => {
t.is(p, extended);
},
"readFileSync": (p) => {
switch (p) {
case file:
t.is(p, file);
return JSON.stringify(fileContent);
case extended:
t.is(p, extended);
return JSON.stringify(extendedContent);
default:
return t.fail();
}
}
};
const actual = markdownlint.readConfigSync(file, null, fsApi);
const expected = {
...extendedContent,
...fileContent
};
delete expected.extends;
t.deepEqual(actual, expected, "Config object not correct.");
});
test("configBadHybridSync", (t) => { test("configBadHybridSync", (t) => {
t.plan(1); t.plan(1);
t.throws( t.throws(
@ -1385,6 +1506,48 @@ test.cb("configSinglePromise", (t) => {
}); });
}); });
test.cb("configCustomFileSystemPromise", (t) => {
t.plan(4);
const file = "/dir/file.json";
const extended = "/dir/extended.json";
const fileContent = {
"extends": extended,
"default": true,
"MD001": false
};
const extendedContent = {
"MD001": true,
"MD002": true
};
const fsApi = {
"accessSync": (p) => {
t.is(p, extended);
},
"readFile": (p, o, cb) => {
switch (p) {
case file:
t.is(p, file);
return cb(null, JSON.stringify(fileContent));
case extended:
t.is(p, extended);
return cb(null, JSON.stringify(extendedContent));
default:
return t.fail();
}
}
};
markdownlint.promises.readConfig(file, null, fsApi)
.then((actual) => {
const expected = {
...extendedContent,
...fileContent
};
delete expected.extends;
t.deepEqual(actual, expected, "Config object not correct.");
t.end();
});
});
test.cb("configBadFilePromise", (t) => { test.cb("configBadFilePromise", (t) => {
t.plan(2); t.plan(2);
markdownlint.promises.readConfig("./test/config/config-badfile.json") markdownlint.promises.readConfig("./test/config/config-badfile.json")