You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
3.9 KiB
172 lines
3.9 KiB
'use strict';
|
|
const isOptionObject = require('is-plain-obj');
|
|
|
|
const {hasOwnProperty} = Object.prototype;
|
|
const {propertyIsEnumerable} = Object;
|
|
const defineProperty = (object, name, value) => Object.defineProperty(object, name, {
|
|
value,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
|
|
const globalThis = this;
|
|
const defaultMergeOptions = {
|
|
concatArrays: false,
|
|
ignoreUndefined: false
|
|
};
|
|
|
|
const getEnumerableOwnPropertyKeys = value => {
|
|
const keys = [];
|
|
|
|
for (const key in value) {
|
|
if (hasOwnProperty.call(value, key)) {
|
|
keys.push(key);
|
|
}
|
|
}
|
|
|
|
/* istanbul ignore else */
|
|
if (Object.getOwnPropertySymbols) {
|
|
const symbols = Object.getOwnPropertySymbols(value);
|
|
|
|
for (const symbol of symbols) {
|
|
if (propertyIsEnumerable.call(value, symbol)) {
|
|
keys.push(symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
return keys;
|
|
};
|
|
|
|
function clone(value) {
|
|
if (Array.isArray(value)) {
|
|
return cloneArray(value);
|
|
}
|
|
|
|
if (isOptionObject(value)) {
|
|
return cloneOptionObject(value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function cloneArray(array) {
|
|
const result = array.slice(0, 0);
|
|
|
|
getEnumerableOwnPropertyKeys(array).forEach(key => {
|
|
defineProperty(result, key, clone(array[key]));
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
function cloneOptionObject(object) {
|
|
const result = Object.getPrototypeOf(object) === null ? Object.create(null) : {};
|
|
|
|
getEnumerableOwnPropertyKeys(object).forEach(key => {
|
|
defineProperty(result, key, clone(object[key]));
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @param {*} merged already cloned
|
|
* @param {*} source something to merge
|
|
* @param {string[]} keys keys to merge
|
|
* @param {Object} config Config Object
|
|
* @returns {*} cloned Object
|
|
*/
|
|
const mergeKeys = (merged, source, keys, config) => {
|
|
keys.forEach(key => {
|
|
if (typeof source[key] === 'undefined' && config.ignoreUndefined) {
|
|
return;
|
|
}
|
|
|
|
// Do not recurse into prototype chain of merged
|
|
if (key in merged && merged[key] !== Object.getPrototypeOf(merged)) {
|
|
defineProperty(merged, key, merge(merged[key], source[key], config));
|
|
} else {
|
|
defineProperty(merged, key, clone(source[key]));
|
|
}
|
|
});
|
|
|
|
return merged;
|
|
};
|
|
|
|
/**
|
|
* @param {*} merged already cloned
|
|
* @param {*} source something to merge
|
|
* @param {Object} config Config Object
|
|
* @returns {*} cloned Object
|
|
*
|
|
* see [Array.prototype.concat ( ...arguments )](http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.concat)
|
|
*/
|
|
const concatArrays = (merged, source, config) => {
|
|
let result = merged.slice(0, 0);
|
|
let resultIndex = 0;
|
|
|
|
[merged, source].forEach(array => {
|
|
const indices = [];
|
|
|
|
// `result.concat(array)` with cloning
|
|
for (let k = 0; k < array.length; k++) {
|
|
if (!hasOwnProperty.call(array, k)) {
|
|
continue;
|
|
}
|
|
|
|
indices.push(String(k));
|
|
|
|
if (array === merged) {
|
|
// Already cloned
|
|
defineProperty(result, resultIndex++, array[k]);
|
|
} else {
|
|
defineProperty(result, resultIndex++, clone(array[k]));
|
|
}
|
|
}
|
|
|
|
// Merge non-index keys
|
|
result = mergeKeys(result, array, getEnumerableOwnPropertyKeys(array).filter(key => !indices.includes(key)), config);
|
|
});
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* @param {*} merged already cloned
|
|
* @param {*} source something to merge
|
|
* @param {Object} config Config Object
|
|
* @returns {*} cloned Object
|
|
*/
|
|
function merge(merged, source, config) {
|
|
if (config.concatArrays && Array.isArray(merged) && Array.isArray(source)) {
|
|
return concatArrays(merged, source, config);
|
|
}
|
|
|
|
if (!isOptionObject(source) || !isOptionObject(merged)) {
|
|
return clone(source);
|
|
}
|
|
|
|
return mergeKeys(merged, source, getEnumerableOwnPropertyKeys(source), config);
|
|
}
|
|
|
|
module.exports = function (...options) {
|
|
const config = merge(clone(defaultMergeOptions), (this !== globalThis && this) || {}, defaultMergeOptions);
|
|
let merged = {_: {}};
|
|
|
|
for (const option of options) {
|
|
if (option === undefined) {
|
|
continue;
|
|
}
|
|
|
|
if (!isOptionObject(option)) {
|
|
throw new TypeError('`' + option + '` is not an Option Object');
|
|
}
|
|
|
|
merged = merge(merged, {_: option}, config);
|
|
}
|
|
|
|
return merged._;
|
|
};
|