-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(clone): enhance type and logic. #206
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,48 +26,103 @@ | |
* console.log(clonedObj); // { a: 1, b: 'es-toolkit', c: [1, 2, 3] } | ||
* console.log(clonedObj === obj); // false | ||
*/ | ||
export function clone<T>(obj: T): T { | ||
export function clone<T>(obj: T): Shallowed<T> { | ||
samchon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (isPrimitive(obj)) { | ||
return obj; | ||
return obj as Shallowed<T>; | ||
} | ||
|
||
if (Array.isArray(obj)) { | ||
return obj.slice() as T; | ||
return obj.slice() as Shallowed<T>; | ||
} | ||
|
||
if (obj instanceof Date) { | ||
return new Date(obj.getTime()) as T; | ||
return new Date(obj.getTime()) as Shallowed<T>; | ||
} | ||
|
||
if (obj instanceof RegExp) { | ||
return new RegExp(obj.source, obj.flags) as T; | ||
return new RegExp(obj.source, obj.flags) as Shallowed<T>; | ||
} | ||
|
||
if (obj instanceof Map) { | ||
const result = new Map(); | ||
for (const [key, value] of obj) { | ||
result.set(key, value); | ||
} | ||
return result as T; | ||
return result as Shallowed<T>; | ||
} | ||
|
||
if (obj instanceof Set) { | ||
const result = new Set(); | ||
for (const value of obj) { | ||
result.add(value); | ||
} | ||
return result as T; | ||
return result as Shallowed<T>; | ||
} | ||
|
||
if (typeof obj === "object") { | ||
return Object.assign({}, obj) as T; | ||
// BINARY DATA | ||
if (obj instanceof Uint8Array) return new Uint8Array(obj) as Shallowed<T>; | ||
if (obj instanceof Uint8ClampedArray) return new Uint8ClampedArray(obj) as Shallowed<T>; | ||
if (obj instanceof Uint16Array) return new Uint16Array(obj) as Shallowed<T>; | ||
if (obj instanceof Uint32Array) return new Uint32Array(obj) as Shallowed<T>; | ||
if (obj instanceof BigUint64Array) return new BigUint64Array(obj) as Shallowed<T>; | ||
if (obj instanceof Int8Array) return new Int8Array(obj) as Shallowed<T>; | ||
if (obj instanceof Int16Array) return new Int16Array(obj) as Shallowed<T>; | ||
if (obj instanceof Int32Array) return new Int32Array(obj) as Shallowed<T>; | ||
if (obj instanceof BigInt64Array) return new BigInt64Array(obj) as Shallowed<T>; | ||
if (obj instanceof Float32Array) return new Float32Array(obj) as Shallowed<T>; | ||
if (obj instanceof Float64Array) return new Float64Array(obj) as Shallowed<T>; | ||
if (obj instanceof ArrayBuffer) return obj.slice(0) as Shallowed<T>; | ||
if (obj instanceof SharedArrayBuffer) return obj.slice(0) as Shallowed<T>; | ||
if (obj instanceof DataView) return new DataView(obj.buffer.slice(0)) as Shallowed<T>; | ||
if (obj instanceof File) return new File([obj], obj.name, { type: obj.type }) as Shallowed<T>; | ||
if (obj instanceof Blob) return new Blob([obj], { type: obj.type }) as Shallowed<T>; | ||
|
||
if (typeof obj === 'object') { | ||
return Object.assign({}, obj) as Shallowed<T>; | ||
} | ||
return obj; | ||
return obj as Shallowed<T>; | ||
} | ||
|
||
export type Shallowed<T> = Equal<T, ShallowMain<T>> extends true ? T : ShallowMain<T>; | ||
type ShallowMain<T> = T extends [never] | ||
? never | ||
: T extends object | ||
? T extends | ||
| Array<any> | ||
| Set<any> | ||
| Map<any, any> | ||
| Date | ||
| RegExp | ||
| Date | ||
| Uint8Array | ||
| Uint8ClampedArray | ||
| Uint16Array | ||
| Uint32Array | ||
| BigUint64Array | ||
| Int8Array | ||
| Int16Array | ||
| Int32Array | ||
| BigInt64Array | ||
| Float32Array | ||
| Float64Array | ||
| ArrayBuffer | ||
| SharedArrayBuffer | ||
| DataView | ||
| Blob | ||
| File | ||
? T | ||
: OmitNever<{ | ||
[P in keyof T]: T[P] extends Function ? never : T[P]; | ||
}> | ||
Comment on lines
+120
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what this type does here. Could you explain what this is doing? I thought cloning some objects with methods, like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There could be many edge cases, so we might just return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A developer working on the code might know if a value has been shallow copied, but other collaborators may not be aware without inspecting the code (specifically, the function returning the cloned value). Therefore, I believe the type proposed by samchon can help reduce misunderstandings among developers. Using this type enhances code readability and prevents unnecessary bugs and confusion within the team. By employing the Resolved type, we can avoid the misconception of certain methods existing on plain objects, ensuring that developers understand the correct nature of the cloned value and can use the code more accurately. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that I was wondering what will happen to As far as I know, we cannot easily distinguish class methods and function properties, since TypeScript uses structural typing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
To fix the problem, changed the implementation code. return Object.fromEntries(
Object.entries(obj).filter(([_key, value]) => value !== undefined && typeof value !== "function")
) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think changing the implementation is awkward since most people would expect that const object = { foo: () => {} };
const cloned = { ...object }; I think types should follow the implementation. Implementation should not follow the types. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion, unless there is a TypeScript type that simply handles this case, we allow this behavior, because I think cloning a class instance is not a major use case. Please refer to our contributing guide where we value simple implementation, and our functions will have complex implementation to suit every usecase. |
||
: T; | ||
|
||
type Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false; | ||
samchon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
type OmitNever<T extends object> = Omit<T, SpecialFields<T, never>>; | ||
type Primitive = null | undefined | string | number | boolean | symbol | bigint; | ||
type SpecialFields<Instance extends object, Target> = { | ||
[P in keyof Instance]: Instance[P] extends Target ? P : never; | ||
}[keyof Instance & string]; | ||
|
||
function isPrimitive(value: unknown): value is Primitive { | ||
return value == null || | ||
(typeof value !== "object" && typeof value !== "function"); | ||
return value == null || (typeof value !== 'object' && typeof value !== 'function'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this has to be tested with some other type testing library.
See vitest's docs for details.