-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
154 lines (136 loc) · 3.78 KB
/
index.js
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"use strict";
const {
getOwnPropertyDescriptor,
defineProperty,
getPrototypeOf,
ownKeys,
} = Reflect;
const { keys: objectKeys, hasOwnProperty } = Object;
const enumerators = {
enumerable: enumerableEnumerator,
ownKeys: ownKeysEnumerator,
keys: keysEnumerator,
};
const chainEnumerators = {
enumerable: chainEnumerableEnumerator,
ownKeys: chainOwnKeysEnumerator,
keys: chainKeysEnumerator,
};
function verifyOptions(options) {
if (typeof options !== "object" || options === null) {
throw new TypeError(`options must be a non-null object`);
}
if (!(options.enumerator in enumerators)) {
throw new TypeError(
`options.enumerator must be one of: 'enumerable', 'ownKeys', 'keys'`
);
}
if (typeof options.chain !== "boolean") {
throw new TypeError(`options.chain must be a boolean`);
}
if (typeof options.descriptor !== "boolean") {
throw new TypeError(`options.descriptor must be a boolean`);
}
}
function* enumerableEnumerator(object) {
for (const propName in object) {
if (hasOwnProperty.call(object, propName)) {
yield propName;
}
}
}
function* chainEnumerableEnumerator(object) {
for (const propName in object) {
yield propName;
}
}
function ownKeysEnumerator(object) {
return ownKeys(object);
}
function chainOwnKeysEnumerator(object, seen = new Set()) {
for (const propName of ownKeys(object)) {
seen.add(propName);
}
const proto = getPrototypeOf(object);
if (proto) {
chainOwnKeysEnumerator(proto, seen);
}
return seen;
}
function keysEnumerator(object) {
return objectKeys(object);
}
function chainKeysEnumerator(object, seen = new Set()) {
for (const propName of objectKeys(object)) {
seen.add(propName);
}
const proto = getPrototypeOf(object);
if (proto) {
chainKeysEnumerator(proto, seen);
}
return seen;
}
function getChainDescriptor(obj, propName) {
if (hasOwnProperty.call(obj, propName)) {
return getOwnPropertyDescriptor(obj, propName);
} else {
const proto = getPrototypeOf(obj);
/* istanbul ignore else */
if (proto !== null) {
return getChainDescriptor(proto, propName);
} else {
// If we ever get here, we somehow decided to copy a property that doesn't
// exist anywhere in the prototype chain. This shouldn't happen ever,
// since our enumerators could never give us this, but if it does, we
// ought to throw.
throw new Error("Got to a null prototype when looking for a property!");
}
}
}
function makeCopyDescriptor(descriptorGetter) {
return (dest, src, propName) =>
defineProperty(dest, propName, descriptorGetter(src, propName));
}
function copyEvaluatedProperty(dest, src, propName) {
dest[propName] = src[propName];
}
function makeSingleCopy(options) {
const enumerator = (options.chain ? chainEnumerators : enumerators)[
options.enumerator
];
const copyProperty = options.descriptor
? makeCopyDescriptor(
options.chain ? getChainDescriptor : getOwnPropertyDescriptor
)
: copyEvaluatedProperty;
return (dest, src) => {
for (const propName of enumerator(src)) {
copyProperty(dest, src, propName);
}
};
}
function makeMultiCopy(singleCopy) {
return (...objects) => {
if (objects.length < 2) {
throw new TypeError("need at least two object inputs!");
}
let destObj;
for (const object of objects) {
if (typeof object !== "object" || object === null) {
throw new TypeError("all inputs must be non-null objects!");
}
if (destObj) {
singleCopy(destObj, object);
} else {
destObj = object;
}
}
return destObj;
};
}
function createCopier(options) {
verifyOptions(options);
const singleCopy = makeSingleCopy(options);
return makeMultiCopy(singleCopy);
}
module.exports = createCopier;