diff --git a/src/deepmerge.ts b/src/deepmerge.ts index cfb6afb..b813efd 100644 --- a/src/deepmerge.ts +++ b/src/deepmerge.ts @@ -1,60 +1,22 @@ import isPlainObj from 'is-plain-obj' +import { + cloneUnlessOtherwiseSpecified, + getKeys, + getMergeFunction, + propertyIsOnObject, + propertyIsUnsafe +} from './utils' function defaultIsMergeable(value) { return Array.isArray(value) || isPlainObj(value) } -function emptyTarget(val) { - return Array.isArray(val) ? [] : {} -} - -function cloneUnlessOtherwiseSpecified(value, options) { - return (options.clone !== false && options.isMergeable(value)) - ? deepmergeImpl(emptyTarget(value), value, options) - : value -} - function defaultArrayMerge(target, source, options) { return target.concat(source).map((element) => cloneUnlessOtherwiseSpecified(element, options) ) } -function getMergeFunction(key, options) { - if (!options.customMerge) { - return deepmergeImpl - } - const customMerge = options.customMerge(key) - return typeof customMerge === 'function' ? customMerge : deepmergeImpl -} - -function getEnumerableOwnPropertySymbols(target) { - return Object.getOwnPropertySymbols - ? Object.getOwnPropertySymbols(target).filter((symbol) => - target.propertyIsEnumerable(symbol) - ) - : [] -} - -function getKeys(target) { - return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target)) -} - -function propertyIsOnObject(object, property) { - try { - return property in object - } catch(_) { - return false - } -} - -// Protects from prototype poisoning and unexpected merging up the prototype chain. -function propertyIsUnsafe(target, key) { - return propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet, - && !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain, - && Object.propertyIsEnumerable.call(target, key)) // and also unsafe if they're nonenumerable. -} - function mergeObject(target, source, options) { const destination = {} if (options.isMergeable(target)) { @@ -76,7 +38,7 @@ function mergeObject(target, source, options) { return destination } -function deepmergeImpl(target, source, options) { +export function deepmergeImpl(target, source, options) { const sourceIsArray = Array.isArray(source) const targetIsArray = Array.isArray(target) const sourceAndTargetTypesMatch = sourceIsArray === targetIsArray diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..a040967 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,45 @@ +import { deepmergeImpl } from './deepmerge'; + +function emptyTarget(value) { + return Array.isArray(value) ? [] : {} +} + +export function cloneUnlessOtherwiseSpecified(value, options) { + return (options.clone !== false && options.isMergeable(value)) + ? deepmergeImpl(emptyTarget(value), value, options) + : value +} + +function getEnumerableOwnPropertySymbols(target) { + return Object.getOwnPropertySymbols + ? Object.getOwnPropertySymbols(target).filter((symbol) => target.propertyIsEnumerable(symbol) + ) + : []; +} + +export function getKeys(target) { + return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target)); +} + +export function propertyIsOnObject(object, property) { + try { + return property in object; + } catch (_) { + return false; + } +} + +export function getMergeFunction(key, options) { + if (!options.customMerge) { + return deepmergeImpl + } + const customMerge = options.customMerge(key) + return typeof customMerge === 'function' ? customMerge : deepmergeImpl +} + +// Protects from prototype poisoning and unexpected merging up the prototype chain. +export function propertyIsUnsafe(target, key) { + return propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet, + && !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain, + && Object.propertyIsEnumerable.call(target, key)) // and also unsafe if they're nonenumerable. +}