diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index c0373d9c..3d87b7f4 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -2828,6 +2828,53 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) { return resolvedExtendsFile; } +/** + * Extend specified configuration object. + * + * @param {Configuration} config Configuration object. + * @param {string} file Configuration file name. + * @param {ConfigurationParser[]} parsers Parsing + * function(s). + * @param {Object} fs File system implementation. + * @param {ReadConfigCallback} callback Callback (err, result) function. + * @returns {void} + */ +function extendConfig(config, file, parsers, fs, callback) { + var configExtends = config["extends"]; + if (configExtends) { + return resolveConfigExtends(file, helpers.expandTildePath(configExtends, __webpack_require__(/*! node:os */ "?e6c4")), fs, + // eslint-disable-next-line no-use-before-define + function (_, resolvedExtends) { + return readConfig( + // @ts-ignore + resolvedExtends, parsers, fs, function (err, extendsConfig) { + if (err) { + return callback(err); + } + var result = _objectSpread(_objectSpread({}, extendsConfig), config); + delete result["extends"]; + return callback(null, result); + }); + }); + } + return callback(null, config); +} +var extendConfigPromisify = promisify && promisify(extendConfig); + +/** + * Extend specified configuration object. + * + * @param {Configuration} config Configuration object. + * @param {string} file Configuration file name. + * @param {ConfigurationParser[]} [parsers] Parsing function(s). + * @param {Object} [fs] File system implementation. + * @returns {Promise} Configuration object. + */ +function extendConfigPromise(config, file, parsers, fs) { + // @ts-ignore + return extendConfigPromisify(config, file, parsers, fs); +} + /** * Read specified configuration file. * @@ -2854,8 +2901,7 @@ function readConfig(file, parsers, fs, callback) { fs = __webpack_require__(/*! node:fs */ "?d0ee"); } // Read file - var os = __webpack_require__(/*! node:os */ "?e6c4"); - file = helpers.expandTildePath(file, os); + file = helpers.expandTildePath(file, __webpack_require__(/*! node:os */ "?e6c4")); fs.readFile(file, "utf8", function (err, content) { if (err) { // @ts-ignore @@ -2871,24 +2917,8 @@ function readConfig(file, parsers, fs, callback) { return callback(new Error(message)); } // Extend configuration - var configExtends = config["extends"]; - if (configExtends) { - delete config["extends"]; - return resolveConfigExtends(file, helpers.expandTildePath(configExtends, os), fs, function (_, resolvedExtends) { - return readConfig( - // @ts-ignore - resolvedExtends, parsers, fs, function (errr, extendsConfig) { - if (errr) { - // @ts-ignore - return callback(errr); - } - // @ts-ignore - return callback(null, _objectSpread(_objectSpread({}, extendsConfig), config)); - }); - }); - } // @ts-ignore - return callback(null, config); + return extendConfig(config, file, parsers, fs, callback); }); } var readConfigPromisify = promisify && promisify(readConfig); @@ -2956,6 +2986,7 @@ markdownlint.readConfigSync = readConfigSync; markdownlint.getVersion = getVersion; markdownlint.promises = { "markdownlint": markdownlintPromise, + "extendConfig": extendConfigPromise, "readConfig": readConfigPromise }; module.exports = markdownlint; diff --git a/lib/markdownlint.d.ts b/lib/markdownlint.d.ts index 43aafd3b..a3f72429 100644 --- a/lib/markdownlint.d.ts +++ b/lib/markdownlint.d.ts @@ -101,6 +101,7 @@ declare function readConfigSync(file: string, parsers?: ConfigurationParser[], f declare function getVersion(): string; declare namespace promises { export { markdownlintPromise as markdownlint }; + export { extendConfigPromise as extendConfig }; export { readConfigPromise as readConfig }; } /** @@ -378,6 +379,16 @@ type ResolveConfigExtendsCallback = (err: Error | null, path?: string) => void; * @returns {Promise} Results object. */ declare function markdownlintPromise(options: Options): Promise; +/** + * Extend specified configuration object. + * + * @param {Configuration} config Configuration object. + * @param {string} file Configuration file name. + * @param {ConfigurationParser[]} [parsers] Parsing function(s). + * @param {Object} [fs] File system implementation. + * @returns {Promise} Configuration object. + */ +declare function extendConfigPromise(config: Configuration, file: string, parsers?: ConfigurationParser[], fs?: any): Promise; /** * Read specified configuration file. * diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 8fe2dded..3e7ee819 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -1079,6 +1079,63 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) { return resolvedExtendsFile; } +/** + * Extend specified configuration object. + * + * @param {Configuration} config Configuration object. + * @param {string} file Configuration file name. + * @param {ConfigurationParser[]} parsers Parsing + * function(s). + * @param {Object} fs File system implementation. + * @param {ReadConfigCallback} callback Callback (err, result) function. + * @returns {void} + */ +function extendConfig(config, file, parsers, fs, callback) { + const configExtends = config.extends; + if (configExtends) { + return resolveConfigExtends( + file, + helpers.expandTildePath(configExtends, require("node:os")), + fs, + // eslint-disable-next-line no-use-before-define + (_, resolvedExtends) => readConfig( + // @ts-ignore + resolvedExtends, + parsers, + fs, + (err, extendsConfig) => { + if (err) { + return callback(err); + } + const result = { + ...extendsConfig, + ...config + }; + delete result.extends; + return callback(null, result); + } + ) + ); + } + return callback(null, config); +} + +const extendConfigPromisify = promisify && promisify(extendConfig); + +/** + * Extend specified configuration object. + * + * @param {Configuration} config Configuration object. + * @param {string} file Configuration file name. + * @param {ConfigurationParser[]} [parsers] Parsing function(s). + * @param {Object} [fs] File system implementation. + * @returns {Promise} Configuration object. + */ +function extendConfigPromise(config, file, parsers, fs) { + // @ts-ignore + return extendConfigPromisify(config, file, parsers, fs); +} + /** * Read specified configuration file. * @@ -1105,8 +1162,7 @@ function readConfig(file, parsers, fs, callback) { fs = require("node:fs"); } // Read file - const os = require("node:os"); - file = helpers.expandTildePath(file, os); + file = helpers.expandTildePath(file, require("node:os")); fs.readFile(file, "utf8", (err, content) => { if (err) { // @ts-ignore @@ -1120,34 +1176,8 @@ function readConfig(file, parsers, fs, callback) { return callback(new Error(message)); } // Extend configuration - const configExtends = config.extends; - if (configExtends) { - delete config.extends; - return resolveConfigExtends( - file, - helpers.expandTildePath(configExtends, os), - fs, - (_, resolvedExtends) => readConfig( - // @ts-ignore - resolvedExtends, - parsers, - fs, - (errr, extendsConfig) => { - if (errr) { - // @ts-ignore - return callback(errr); - } - // @ts-ignore - return callback(null, { - ...extendsConfig, - ...config - }); - } - ) - ); - } // @ts-ignore - return callback(null, config); + return extendConfig(config, file, parsers, fs, callback); }); } @@ -1221,6 +1251,7 @@ markdownlint.readConfigSync = readConfigSync; markdownlint.getVersion = getVersion; markdownlint.promises = { "markdownlint": markdownlintPromise, + "extendConfig": extendConfigPromise, "readConfig": readConfigPromise }; module.exports = markdownlint; diff --git a/test/markdownlint-test-config.js b/test/markdownlint-test-config.js index 78ef3f7f..46f430d9 100644 --- a/test/markdownlint-test-config.js +++ b/test/markdownlint-test-config.js @@ -55,6 +55,7 @@ test("configMultiple", (t) => new Promise((resolve) => { ...require("./config/config-parent.json"), ...require("./config/config-grandparent.json") }; + // @ts-ignore delete expected.extends; t.deepEqual(actual, expected, "Config object not correct."); resolve(); @@ -70,6 +71,7 @@ test("configMultipleWithRequireResolve", (t) => new Promise((resolve) => { ...require("./node_modules/pseudo-package/config-frompackage.json"), ...require("./config/config-packageparent.json") }; + // @ts-ignore delete expected.extends; t.deepEqual(actual, expected, "Config object not correct."); resolve(); @@ -106,6 +108,7 @@ test("configCustomFileSystem", (t) => new Promise((resolve) => { }; markdownlint.readConfig( file, + // @ts-ignore null, fsApi, function callback(err, actual) { @@ -114,6 +117,7 @@ test("configCustomFileSystem", (t) => new Promise((resolve) => { ...extendedContent, ...fileContent }; + // @ts-ignore delete expected.extends; t.deepEqual(actual, expected, "Config object not correct."); resolve(); @@ -210,6 +214,7 @@ test("configMultipleYaml", (t) => new Promise((resolve) => { ...require("./config/config-parent.json"), ...require("./config/config-grandparent.json") }; + // @ts-ignore delete expected.extends; t.deepEqual(actual, expected, "Config object not correct."); resolve(); @@ -229,6 +234,7 @@ test("configMultipleHybrid", (t) => new Promise((resolve) => { ...require("./config/config-parent.json"), ...require("./config/config-grandparent.json") }; + // @ts-ignore delete expected.extends; t.like(actual, expected, "Config object not correct."); resolve(); @@ -287,6 +293,7 @@ test("configMultipleSync", (t) => { ...require("./config/config-parent.json"), ...require("./config/config-grandparent.json") }; + // @ts-ignore delete expected.extends; t.deepEqual(actual, expected, "Config object not correct."); }); @@ -366,6 +373,7 @@ test("configMultipleYamlSync", (t) => { ...require("./config/config-parent.json"), ...require("./config/config-grandparent.json") }; + // @ts-ignore delete expected.extends; t.deepEqual(actual, expected, "Config object not correct."); }); @@ -381,6 +389,7 @@ test("configMultipleHybridSync", (t) => { ...require("./config/config-parent.json"), ...require("./config/config-grandparent.json") }; + // @ts-ignore delete expected.extends; t.like(actual, expected, "Config object not correct."); }); @@ -412,11 +421,12 @@ test("configCustomFileSystemSync", (t) => { return t.fail(p); } }; - const actual = markdownlint.readConfigSync(file, null, fsApi); + const actual = markdownlint.readConfigSync(file, undefined, fsApi); const expected = { ...extendedContent, ...fileContent }; + // @ts-ignore delete expected.extends; t.deepEqual(actual, expected, "Config object not correct."); }); @@ -479,12 +489,13 @@ test("configCustomFileSystemPromise", (t) => new Promise((resolve) => { } } }; - markdownlint.promises.readConfig(file, null, fsApi) + markdownlint.promises.readConfig(file, undefined, fsApi) .then((actual) => { const expected = { ...extendedContent, ...fileContent }; + // @ts-ignore delete expected.extends; t.deepEqual(actual, expected, "Config object not correct."); resolve(); @@ -503,3 +514,57 @@ test("configBadFilePromise", (t) => new Promise((resolve) => { } ); })); + +test("extendSinglePromise", (t) => new Promise((resolve) => { + t.plan(1); + const expected = require("./config/config-child.json"); + markdownlint.promises.extendConfig( + expected, "./test/config/config-child.json" + ) + .then((actual) => { + t.deepEqual(actual, expected, "Config object not correct."); + resolve(); + }); +})); + +test("extendCustomFileSystemPromise", (t) => new Promise((resolve) => { + t.plan(4); + const file = path.resolve("/dir/file.json"); + const extended = path.resolve("/dir/extended.json"); + const fileContent = { + "extends": extended, + "default": true, + "MD001": false + }; + const extendedContent = { + "MD001": true, + "MD002": true + }; + const fsApi = { + "access": (p, m, cb) => { + t.is(p, extended); + return (cb || m)(); + }, + "readFile": (p, o, cb) => { + switch (p) { + case extended: + t.is(p, extended); + return cb(null, JSON.stringify(extendedContent)); + default: + return t.fail(); + } + } + }; + markdownlint.promises.extendConfig(fileContent, file, undefined, fsApi) + .then((actual) => { + t.truthy(fileContent.extends); + const expected = { + ...extendedContent, + ...fileContent + }; + // @ts-ignore + delete expected.extends; + t.deepEqual(actual, expected, "Config object not correct."); + resolve(); + }); +}));