diff --git a/src/config-array.js b/src/config-array.js index c08391d..60f0293 100644 --- a/src/config-array.js +++ b/src/config-array.js @@ -50,13 +50,19 @@ class ConfigError extends Error { * @param {number} index The index of the config object in the array. * @param {Error} source The source error. */ - constructor(name, index, source) { - super(`Config ${name}: ${source.message}`, { cause: source }); + constructor(name, index, { cause, message }) { + + + const finalMessage = message || cause.message; + + super(`Config ${name}: ${finalMessage}`, { cause }); // copy over custom properties that aren't represented - for (const key of Object.keys(source)) { - if (!(key in this)) { - this[key] = source[key]; + if (cause) { + for (const key of Object.keys(cause)) { + if (!(key in this)) { + this[key] = cause[key]; + } } } @@ -82,14 +88,13 @@ class ConfigError extends Error { * @returns {string} The name of the config object. */ function getConfigName(config) { - if (typeof config.name === 'string' && config.name) { + if (config && typeof config.name === 'string' && config.name) { return `"${config.name}"`; } return '(unnamed)'; } - /** * Rethrows a config error with additional information about the config object. * @param {object} config The config object to get the name of. @@ -112,19 +117,28 @@ function isString(value) { } /** - * Creates a function that asserts that the files and ignores keys - * of a config object are valid as per base schema. + * Creates a function that asserts that the config is valid + * during normalization. This checks that the config is not nullish + * and that files and ignores keys of a config object are valid as per base schema. * @param {Object} config The config object to check. * @param {number} index The index of the config object in the array. * @returns {void} * @throws {ConfigError} If the files and ignores keys of a config object are not valid. */ -function assertValidFilesAndIgnores(config, index) { +function assertValidBaseConfig(config, index) { - if (!config || typeof config !== 'object') { - return; + if (config === null) { + throw new ConfigError(getConfigName(config), index, { message: 'Unexpected null config.' }); } - + + if (config === undefined) { + throw new ConfigError(getConfigName(config), index, { message: 'Unexpected undefined config.' }); + } + + if (typeof config !== 'object') { + throw new ConfigError(getConfigName(config), index, { message: 'Unexpected non-object config.' }); + } + const validateConfig = { }; if ('files' in config) { @@ -138,7 +152,7 @@ function assertValidFilesAndIgnores(config, index) { try { FILES_AND_IGNORES_SCHEMA.validate(validateConfig); } catch (validationError) { - rethrowConfigError(config, index, validationError); + rethrowConfigError(config, index, { cause: validationError }); } } @@ -634,7 +648,7 @@ export class ConfigArray extends Array { const normalizedConfigs = await normalize(this, context, this.extraConfigTypes); this.length = 0; this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this))); - this.forEach(assertValidFilesAndIgnores); + this.forEach(assertValidBaseConfig); this[ConfigArraySymbol.isNormalized] = true; // prevent further changes @@ -656,7 +670,7 @@ export class ConfigArray extends Array { const normalizedConfigs = normalizeSync(this, context, this.extraConfigTypes); this.length = 0; this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this))); - this.forEach(assertValidFilesAndIgnores); + this.forEach(assertValidBaseConfig); this[ConfigArraySymbol.isNormalized] = true; // prevent further changes @@ -892,7 +906,7 @@ export class ConfigArray extends Array { try { return this[ConfigArraySymbol.schema].merge(result, this[index]); } catch (validationError) { - rethrowConfigError(this[index], index, validationError); + rethrowConfigError(this[index], index, { cause: validationError}); } }, {}, this); diff --git a/tests/config-array.test.js b/tests/config-array.test.js index a1363ee..e39b479 100644 --- a/tests/config-array.test.js +++ b/tests/config-array.test.js @@ -416,13 +416,12 @@ describe('ConfigArray', () => { }, 'eslint:reccommended' // typo ], { basePath }); - await configs.normalize(); expect(() => { - configs.getConfig(path.resolve(basePath, 'foo.js')); + configs.normalizeSync(); }) .to - .throw('All arguments must be objects.'); + .throw('Config (unnamed): Unexpected non-object config.'); }); @@ -433,6 +432,7 @@ describe('ConfigArray', () => { name: true } ], { basePath }); + await configs.normalize(); expect(() => { @@ -451,6 +451,7 @@ describe('ConfigArray', () => { name: true } ); + await configs.normalize(); expect(() => { @@ -460,6 +461,52 @@ describe('ConfigArray', () => { .throw('Config (unnamed): Key "name": Property must be a string.'); }); + + it('should throw an error when base config is undefined', async () => { + configs = new ConfigArray([undefined], { basePath }); + + expect(() => { + configs.normalizeSync(); + }) + .to + .throw('Config (unnamed): Unexpected undefined config.'); + + }); + + it('should throw an error when base config is null', async () => { + configs = new ConfigArray([null], { basePath }); + + expect(() => { + configs.normalizeSync(); + }) + .to + .throw('Config (unnamed): Unexpected null config.'); + + }); + + it('should throw an error when additional config is undefined', async () => { + configs = new ConfigArray([{}], { basePath }); + configs.push(undefined); + + expect(() => { + configs.normalizeSync(); + }) + .to + .throw('Config (unnamed): Unexpected undefined config.'); + + }); + + it('should throw an error when additional config is null', async () => { + configs = new ConfigArray([{}], { basePath }); + configs.push(null); + + expect(() => { + configs.normalizeSync(); + }) + .to + .throw('Config (unnamed): Unexpected null config.'); + + }); }); describe('ConfigArray members', () => {