All files / src merge.ts

100% Statements 42/42
100% Branches 51/51
100% Functions 1/1
100% Lines 39/39
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 1091x 1x 1x   1x                     50x 35x     35x 7x   28x     35x 4x 31x 24x   24x 1x       34x 1x     33x 1x     32x 5x     2x   2x     3x 3x     27x     35x 35x       35x       34x   33x                           26x 26x   8x         7x       2x         7x       22x     1x
import Config           from './Config';
import ArrayStrategy    from './Constants/ArrayStrategy';
import handleMergeError from './handleMergeError';
import IConfig          from './Interfaces/IConfig';
import * as Messages    from './Messages';
 
/**
 * Merges the properties of a source object into a target object.
 *
 * @param  {any}          target  The target object to be merged into.
 * @param  {any}          source  The source object containing the properties to be copied.
 * @param  {IConfig|true} options An optional configuration object, or `true` as a shorthand for `{deep: true}`.
 * @return {any}          A reference to the modified target object.
 */
 
function merge(target: any, source: any, options: IConfig|true = {}): any {
    let sourceKeys: string[] = [];
    let config: Config;
 
    if (options instanceof Config) {
        config = options;
    } else {
        config = new Config();
    }
 
    if (typeof options === 'boolean' && options === true) {
        config.deep = true;
    } else if (options && config !== options && typeof options === 'object') {
        Object.assign(config, options);
 
        if ([ArrayStrategy.PUSH, ArrayStrategy.REPLACE].indexOf(config.arrayStrategy) < 0) {
            throw RangeError(Messages.INVALID_ARRAY_STRATEGY(config.arrayStrategy));
        }
    }
 
    if (!target || typeof target !== 'object') {
        throw new TypeError(Messages.TYPE_ERROR_TARGET(target));
    }
 
    if (!source || typeof source !== 'object') {
        throw new TypeError(Messages.TYPE_ERROR_SOURCE(source));
    }
 
    if (Array.isArray(source)) {
        if (config.arrayStrategy === ArrayStrategy.PUSH) {
            // Merge arrays via push()
 
            target.push(...source);
 
            return target;
        }
 
        for (let i = 0; i < source.length; i++) {
            sourceKeys.push(i.toString());
        }
    } else {
        sourceKeys = Object.getOwnPropertyNames(source);
    }
 
    for (const key of sourceKeys) {
        const descriptor = Object.getOwnPropertyDescriptor(source, key);
 
        // Skip read-only properties
 
        if (typeof descriptor.get === 'function' && !descriptor.set && !config.includeReadOnly) continue;
 
        // Skip non-enumerable properties
 
        if (!descriptor.enumerable && !config.includeNonEmurable) continue;
 
        if (
            !config.deep ||
            typeof source[key] !== 'object' ||
            source[key] === null ||
            (Array.isArray(source[key]) && config.useReferenceIfArray) ||
            (!target[key] && config.useReferenceIfTargetUnset)
        ) {
            // If:
            // - Shallow merge
            // - All non-object primatives
            // - Null pointers
            // - Arrays, if `useReferenceIfArray` set
            // - Target prop null or undefined and `useRererenceIfTargetUnset` set
 
            try {
                target[key] = source[key];
            } catch (err) {
                handleMergeError(err, target, key, config.errorMessage);
            }
        } else {
            // Deep merge objects/arrays
 
            if (!Object.prototype.hasOwnProperty.call(target, key) || target[key] === null) {
                // If property does not exist on target, instantiate an empty
                // object or array to merge into
 
                target[key] = Array.isArray(source[key]) ? [] : {};
            }
 
            // Recursively deep copy objects or arrays
 
            merge(target[key], source[key], config);
        }
    }
 
    return target;
}
 
export default merge;