diff --git a/src/lib/util/merge.js b/src/lib/util/merge.js index d50329b56..3a6058ec7 100644 --- a/src/lib/util/merge.js +++ b/src/lib/util/merge.js @@ -1,11 +1,11 @@ export default function merge(obj = { }, defaults) { - if (typeof obj !== 'object' || obj === null) { - obj = {}; - } + // Copy `obj` instead of mutating it, so that a caller's options object is not + // modified as a side effect (and a frozen options object does not throw). + const result = (typeof obj !== 'object' || obj === null) ? {} : { ...obj }; for (const key in defaults) { - if (typeof obj[key] === 'undefined') { - obj[key] = defaults[key]; + if (typeof result[key] === 'undefined') { + result[key] = defaults[key]; } } - return obj; + return result; } diff --git a/test/validators.test.js b/test/validators.test.js index 3d2c8e8b2..9bbbbd4ec 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -16218,4 +16218,17 @@ describe('Validators', () => { ], }); }); + + it('should not mutate the options object passed to a validator', () => { + const options = {}; + validator.isEmail('foo@bar.com', options); + assert.deepStrictEqual(options, {}, 'options object was mutated'); + }); + + it('should accept a frozen options object', () => { + assert.strictEqual( + validator.isURL('https://example.com', Object.freeze({ require_tld: true })), + true + ); + }); });