Repository: sindresorhus/serialize-error (js)

Issue:
Consider making common error properties non enumerable
## Description

Native JS errors don't have its properties enumerable. What this means is that the `name`, `stack` and `message` properties do not appear when console.logging errors.

Notice the `name` and `message` property appearing below.

![image](https://user-images.githubusercontent.com/25233379/81183679-bf509700-8faf-11ea-8499-ee5f5608aad5.png)

## Proposal

To get deserialized errors closer to the native ones, when deserializing the error, we could use `Object.defineProperty` instead of a direct assignment to make the properties not enumerable. eg: here https://github.com/sindresorhus/serialize-error/blob/master/index.js#L54

```ts
for (const property of commonProperties) {
	if (typeof from[property] === 'string') {
        Object.defineProperty(to, property, {
          value: from[property]
        })
	}
}
```

I'm happy to make a PR if you agree with the proposal!

Thanks for your awesome work across the Node community ❤️

Pull request description:
Make error properties non-enumerable
Closes #29

Patch:
diff --git a/index.d.ts b/index.d.ts
index d9d0d6c..714dc9f 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -37,6 +37,8 @@ Deserialize a plain object or any value into an `Error` object.
 `Error` objects are passed through.
 Non-error values are wrapped in a `NonError` error.
 Custom properties are preserved.
+Non-enumerable properties are kept non-enumerable (name, message, stack).
+Enumerable properties are kept enumerable (all properties besides the non-enumerable ones).
 Circular references are handled.
 
 @example
diff --git a/index.js b/index.js
index 658411e..06a2916 100644
--- a/index.js
+++ b/index.js
@@ -3,7 +3,11 @@
 class NonError extends Error {
 	constructor(message) {
 		super(NonError._prepareSuperMessage(message));
-		this.name = 'NonError';
+		Object.defineProperty(this, 'name', {
+			value: 'NonError',
+			configurable: true,
+			writable: true
+		});
 
 		if (Error.captureStackTrace) {
 			Error.captureStackTrace(this, NonError);
@@ -20,10 +24,10 @@ class NonError extends Error {
 }
 
 const commonProperties = [
-	'name',
-	'message',
-	'stack',
-	'code'
+	{property: 'name', enumerable: false},
+	{property: 'message', enumerable: false},
+	{property: 'stack', enumerable: false},
+	{property: 'code', enumerable: true}
 ];
 
 const destroyCircular = (from, seen, to_) => {
@@ -49,9 +53,14 @@ const destroyCircular = (from, seen, to_) => {
 		to[key] = '[Circular]';
 	}
 
-	for (const property of commonProperties) {
+	for (const {property, enumerable} of commonProperties) {
 		if (typeof from[property] === 'string') {
-			to[property] = from[property];
+			Object.defineProperty(to, property, {
+				value: from[property],
+				enumerable,
+				configurable: true,
+				writable: true
+			});
 		}
 	}
 
diff --git a/readme.md b/readme.md
index aa45241..d27f004 100644
--- a/readme.md
+++ b/readme.md
@@ -39,6 +39,8 @@ Serialize an `Error` object into a plain object.
 
 Non-error values are passed through.
 Custom properties are preserved.
+Non-enumerable properties are kept non-enumerable (name, message, stack).
+Enumerable properties are kept enumerable (all properties besides the non-enumerable ones).
 Circular references are handled.
 
 ### deserializeError(value)


Test patch:
diff --git a/test.js b/test.js
index 6bb512d..dd0e232 100644
--- a/test.js
+++ b/test.js
@@ -10,10 +10,9 @@ function deserializeNonError(t, value) {
 
 test('main', t => {
 	const serialized = serializeError(new Error('foo'));
-	const keys = Object.keys(serialized);
-	t.true(keys.includes('name'));
-	t.true(keys.includes('stack'));
-	t.true(keys.includes('message'));
+	t.true('name' in serialized);
+	t.true('stack' in serialized);
+	t.true('message' in serialized);
 });
 
 test('should destroy circular references', t => {
@@ -172,3 +171,32 @@ test('should deserialize plain object', t => {
 	t.is(deserialized.name, 'name');
 	t.is(deserialized.code, 'code');
 });
+
+test('name, stack and message should not be enumerable, other props should be', t => {
+	const object = {
+		message: 'error message',
+		stack: 'at <anonymous>:1:13',
+		name: 'name'
+	};
+	const nonEnumerableProps = Object.keys(object);
+
+	const enumerables = {
+		code: 'code',
+		path: './path',
+		errno: 1,
+		syscall: 'syscall',
+		randomProperty: 'random'
+	};
+	const enumerableProps = Object.keys(enumerables);
+
+	const deserialized = deserializeError({...object, ...enumerables});
+	const deserializedEnumerableProps = Object.keys(deserialized);
+
+	for (const prop of nonEnumerableProps) {
+		t.false(deserializedEnumerableProps.includes(prop));
+	}
+
+	for (const prop of enumerableProps) {
+		t.true(deserializedEnumerableProps.includes(prop));
+	}
+});