-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(core): Prevent exception recapturing #4067
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
Changes from all commits
178dc25
89add5d
32364c7
a0d0aa7
800675e
3636ee7
db0569e
13c87d3
c62a72f
c9c2985
d409c16
a11c886
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 |
---|---|---|
|
@@ -398,3 +398,40 @@ export function dropUndefinedKeys<T>(val: T): T { | |
|
||
return val; | ||
} | ||
|
||
/** | ||
* Ensure that something is an object. | ||
* | ||
* Turns `undefined` and `null` into `String`s and all other primitives into instances of their respective wrapper | ||
lobsterkatie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* classes (String, Boolean, Number, etc.). Acts as the identity function on non-primitives. | ||
* | ||
* @param wat The subject of the objectification | ||
* @returns A version of `wat` which can safely be used with `Object` class methods | ||
*/ | ||
export function objectify(wat: unknown): typeof Object { | ||
let objectified; | ||
switch (true) { | ||
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 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. This is just a different (and in some cases, cleaner) way to write a long series of |
||
case wat === undefined || wat === null: | ||
objectified = new String(wat); | ||
break; | ||
|
||
// Though symbols and bigints do have wrapper classes (`Symbol` and `BigInt`, respectively), for whatever reason | ||
// those classes don't have constructors which can be used with the `new` keyword. We therefore need to cast each as | ||
// an object in order to wrap it. | ||
case typeof wat === 'symbol' || typeof wat === 'bigint': | ||
objectified = Object(wat); | ||
break; | ||
|
||
// this will catch the remaining primitives: `String`, `Number`, and `Boolean` | ||
case isPrimitive(wat): | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
objectified = new (wat as any).constructor(wat); | ||
iker-barriocanal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
break; | ||
|
||
// by process of elimination, at this point we know that `wat` must already be an object | ||
default: | ||
objectified = wat; | ||
break; | ||
} | ||
return objectified; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,14 @@ | |
*/ | ||
|
||
import * as isModule from '../src/is'; | ||
import { dropUndefinedKeys, extractExceptionKeysForMessage, fill, normalize, urlEncode } from '../src/object'; | ||
import { | ||
dropUndefinedKeys, | ||
extractExceptionKeysForMessage, | ||
fill, | ||
normalize, | ||
objectify, | ||
urlEncode, | ||
} from '../src/object'; | ||
import { testOnlyIfNodeVersionAtLeast } from './testutils'; | ||
|
||
describe('fill()', () => { | ||
|
@@ -638,3 +645,59 @@ describe('dropUndefinedKeys()', () => { | |
}); | ||
}); | ||
}); | ||
|
||
describe('objectify()', () => { | ||
describe('stringifies nullish values', () => { | ||
it.each([ | ||
['undefined', undefined], | ||
['null', null], | ||
])('%s', (stringifiedValue, origValue): void => { | ||
const objectifiedNullish = objectify(origValue); | ||
|
||
expect(objectifiedNullish).toEqual(expect.any(String)); | ||
expect(objectifiedNullish.valueOf()).toEqual(stringifiedValue); | ||
}); | ||
}); | ||
|
||
describe('wraps other primitives with their respective object wrapper classes', () => { | ||
// TODO: There's currently a bug in Jest - if you give it the `Boolean` class, it runs `typeof received === | ||
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. Good comment. 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. Update: They merged the PR and it's released in the latest version of Jest. In a different PR, we can update our dependency and remove this TODO. |
||
// 'boolean'` but not `received instanceof Boolean` (the way it correctly does for other primitive wrappers, like | ||
// `Number` and `String). (See https://github.com/facebook/jest/pull/11976.) Once that is fixed and we upgrade jest, | ||
// we can comment the test below back in. (The tests for symbols and bigints are working only because our current | ||
// version of jest is sufficiently old that they're not even considered in the relevant check and just fall to the | ||
// default `instanceof` check jest uses for all unknown classes.) | ||
|
||
it.each([ | ||
['number', Number, 1121], | ||
['string', String, 'Dogs are great!'], | ||
// ["boolean", Boolean, true], | ||
['symbol', Symbol, Symbol('Maisey')], | ||
])('%s', (_caseName, wrapperClass, primitive) => { | ||
const objectifiedPrimitive = objectify(primitive); | ||
|
||
expect(objectifiedPrimitive).toEqual(expect.any(wrapperClass)); | ||
expect(objectifiedPrimitive.valueOf()).toEqual(primitive); | ||
}); | ||
|
||
// `BigInt` doesn't exist in Node < 10, so we test it separately here. | ||
testOnlyIfNodeVersionAtLeast(10)('bigint', () => { | ||
// Hack to get around the fact that literal bigints cause a syntax error in older versions of Node, so the | ||
// assignment needs to not even be parsed as code in those versions | ||
let bigintPrimitive; | ||
eval('bigintPrimitive = 1231n;'); | ||
|
||
const objectifiedBigInt = objectify(bigintPrimitive); | ||
|
||
expect(objectifiedBigInt).toEqual(expect.any(BigInt)); | ||
expect(objectifiedBigInt.valueOf()).toEqual(bigintPrimitive); | ||
}); | ||
}); | ||
|
||
it('leaves objects alone', () => { | ||
const notAPrimitive = new Object(); | ||
const objectifiedNonPrimtive = objectify(notAPrimitive); | ||
|
||
// `.toBe()` tests on identity, so this shows no wrapping has occurred | ||
expect(objectifiedNonPrimtive).toBe(notAPrimitive); | ||
}); | ||
}); |
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.
thrown
is an object that we get directly from the table, so we should assume the test input is correct and thus, remove this expectation. If you want to clarify it's undefined, you can add a comment.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.
The point of this, for me, is that the test is telling a story through its expect statements, without making the reader have to do any work to fill in the pieces which could be left out because they're logically entailed by other pieces. Here the story is, "The first time around this flag isn't set, we capture the error, the flag is set and we confirm that the call count is 1, we capture the error again, and confirm that the call count is still 1." TL;DR - having the before and after makes it super easy for the reader to see what has or hasn't changed.
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 see. I rather "just testing" without telling a story, and make the tests and their names (both of the
describe
andtest
) be verbose enough to know what should and shouldn't be happening.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.
Yes, agreed. But knowing what should happen (which is what those tell you) isn't the same as proving that it does happen (which is what the
expect
statements show).