Skip to content

ref(utils): Use type predicates in is utility functions #4124

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

Merged
merged 3 commits into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions packages/browser/src/eventbuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,14 @@ export function eventFromUnknownInput(
): Event {
let event: Event;

if (isErrorEvent(exception as ErrorEvent) && (exception as ErrorEvent).error) {
if (isErrorEvent(exception)) {
// If it is an ErrorEvent with `error` property, extract it to get actual Error
const errorEvent = exception as ErrorEvent;
// eslint-disable-next-line no-param-reassign
exception = errorEvent.error;
event = eventFromStacktrace(computeStackTrace(exception as Error));
exception = exception.error;
event = eventFromStacktrace(computeStackTrace(exception));
return event;
}
if (isDOMError(exception as DOMError) || isDOMException(exception as DOMException)) {
if (isDOMError(exception) || isDOMException(exception)) {
// If it is a DOMError or DOMException (which are legacy APIs, but still supported in some browsers)
// then we just extract the name, code, and message, as they don't provide anything else
// https://developer.mozilla.org/en-US/docs/Web/API/DOMError
Expand All @@ -90,9 +89,9 @@ export function eventFromUnknownInput(

return event;
}
if (isError(exception as Error)) {
if (isError(exception)) {
// we have a real Error object, do nothing
event = eventFromStacktrace(computeStackTrace(exception as Error));
event = eventFromStacktrace(computeStackTrace(exception));
return event;
}
if (isPlainObject(exception) || isEvent(exception)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/src/ember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class Ember implements Integration {
getCurrentHub().withScope(scope => {
if (isInstanceOf(reason, Error)) {
scope.setExtra('context', 'Unhandled Promise error detected');
getCurrentHub().captureException(reason, { originalException: reason as Error });
getCurrentHub().captureException(reason, { originalException: reason });
} else {
scope.setExtra('reason', reason);
getCurrentHub().captureMessage('Unhandled Promise error detected');
Expand Down
4 changes: 2 additions & 2 deletions packages/integrations/src/extraerrordata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export class ExtraErrorData implements Integration {
continue;
}
const value = error[key];
extraErrorInfo[key] = isError(value) ? (value as Error).toString() : value;
extraErrorInfo[key] = isError(value) ? value.toString() : value;
}

// Check if someone attached `toJSON` method to grab even more properties (eg. axios is doing that)
Expand All @@ -106,7 +106,7 @@ export class ExtraErrorData implements Integration {

for (const key of Object.keys(serializedError)) {
const value = serializedError[key];
extraErrorInfo[key] = isError(value) ? (value as Error).toString() : value;
extraErrorInfo[key] = isError(value) ? value.toString() : value;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/src/utils/instrumentServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
let traceparentData;
if (req.headers && isString(req.headers['sentry-trace'])) {
traceparentData = extractTraceparentData(req.headers['sentry-trace'] as string);
traceparentData = extractTraceparentData(req.headers['sentry-trace']);
logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/src/utils/withSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const withSentry = (origHandler: NextApiHandler): WrappedNextApiHandler =
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
let traceparentData;
if (req.headers && isString(req.headers['sentry-trace'])) {
traceparentData = extractTraceparentData(req.headers['sentry-trace'] as string);
traceparentData = extractTraceparentData(req.headers['sentry-trace']);
logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/node/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class NodeBackend extends BaseBackend<NodeOptions> {
const message = `Non-Error exception captured with keys: ${extractExceptionKeysForMessage(exception)}`;

getCurrentHub().configureScope(scope => {
scope.setExtra('__serialized__', normalizeToSize(exception as Record<string, unknown>));
scope.setExtra('__serialized__', normalizeToSize(exception));
});

ex = (hint && hint.syntheticException) || new Error(message);
Expand Down
2 changes: 1 addition & 1 deletion packages/node/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function tracingHandler(): (
// If there is a trace header set, we extract the data from it (parentSpanId, traceId, and sampling decision)
let traceparentData;
if (req.headers && isString(req.headers['sentry-trace'])) {
traceparentData = extractTraceparentData(req.headers['sentry-trace'] as string);
traceparentData = extractTraceparentData(req.headers['sentry-trace']);
}

const transaction = startTransaction(
Expand Down
2 changes: 1 addition & 1 deletion packages/serverless/src/awslambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export function wrapHandler<TEvent, TResult>(
let traceparentData;
const eventWithHeaders = event as { headers?: { [key: string]: string } };
if (eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace'])) {
traceparentData = extractTraceparentData(eventWithHeaders.headers['sentry-trace'] as string);
traceparentData = extractTraceparentData(eventWithHeaders.headers['sentry-trace']);
}
const transaction = startTransaction({
name: context.functionName,
Expand Down
2 changes: 1 addition & 1 deletion packages/serverless/src/gcpfunction/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial<HttpFunctionWr
let traceparentData;
const reqWithHeaders = req as { headers?: { [key: string]: string } };
if (reqWithHeaders.headers && isString(reqWithHeaders.headers['sentry-trace'])) {
traceparentData = extractTraceparentData(reqWithHeaders.headers['sentry-trace'] as string);
traceparentData = extractTraceparentData(reqWithHeaders.headers['sentry-trace']);
}
const transaction = startTransaction({
name: `${reqMethod} ${reqUrl}`,
Expand Down
2 changes: 1 addition & 1 deletion packages/tracing/src/browser/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export function fetchCallback(
const options = (handlerData.args[1] = (handlerData.args[1] as { [key: string]: any }) || {});
let headers = options.headers;
if (isInstanceOf(request, Request)) {
headers = (request as Request).headers;
headers = request.headers;
}
if (headers) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Expand Down
2 changes: 1 addition & 1 deletion packages/tracing/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class Transaction extends SpanClass implements TransactionInterface {
super(transactionContext);

if (isInstanceOf(hub, Hub)) {
this._hub = hub as Hub;
this._hub = hub;
}

this.name = transactionContext.name || '';
Expand Down
28 changes: 17 additions & 11 deletions packages/utils/src/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Primitive } from '@sentry/types';
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isError(wat: any): boolean {
export function isError(wat: any): wat is Error {
switch (Object.prototype.toString.call(wat)) {
case '[object Error]':
return true;
Expand All @@ -29,7 +29,7 @@ export function isError(wat: any): boolean {
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isErrorEvent(wat: any): boolean {
export function isErrorEvent(wat: any): wat is ErrorEvent {
return Object.prototype.toString.call(wat) === '[object ErrorEvent]';
}

Expand All @@ -40,7 +40,7 @@ export function isErrorEvent(wat: any): boolean {
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isDOMError(wat: any): boolean {
export function isDOMError(wat: any): wat is DOMError {
return Object.prototype.toString.call(wat) === '[object DOMError]';
}

Expand All @@ -51,18 +51,18 @@ export function isDOMError(wat: any): boolean {
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isDOMException(wat: any): boolean {
export function isDOMException(wat: any): wat is DOMException {
return Object.prototype.toString.call(wat) === '[object DOMException]';
}

/**
* Checks whether given value's type is a string
* Checks whether the given value's type is string
* {@link isString}.
*
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isString(wat: any): boolean {
export function isString(wat: any): wat is string {
return Object.prototype.toString.call(wat) === '[object String]';
}

Expand All @@ -84,7 +84,7 @@ export function isPrimitive(wat: any): wat is Primitive {
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isPlainObject(wat: any): boolean {
export function isPlainObject(wat: any): wat is { [key: string]: unknown } {
return Object.prototype.toString.call(wat) === '[object Object]';
}

Expand All @@ -95,7 +95,7 @@ export function isPlainObject(wat: any): boolean {
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isEvent(wat: any): boolean {
export function isEvent(wat: any): wat is Event {
return typeof Event !== 'undefined' && isInstanceOf(wat, Event);
}

Expand All @@ -106,7 +106,7 @@ export function isEvent(wat: any): boolean {
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isElement(wat: any): boolean {
export function isElement(wat: any): wat is Element {
return typeof Element !== 'undefined' && isInstanceOf(wat, Element);
}

Expand All @@ -117,7 +117,7 @@ export function isElement(wat: any): boolean {
* @param wat A value to be checked.
* @returns A boolean representing the result.
*/
export function isRegExp(wat: any): boolean {
export function isRegExp(wat: any): wat is RegExp {
return Object.prototype.toString.call(wat) === '[object RegExp]';
}

Expand All @@ -140,6 +140,12 @@ export function isThenable(wat: any): boolean {
export function isSyntheticEvent(wat: any): boolean {
return isPlainObject(wat) && 'nativeEvent' in wat && 'preventDefault' in wat && 'stopPropagation' in wat;
}

// Having the two different types is necessary because some types packages type classes as functions (in addition to
// being objects with a `new` method), and some only type them as `new`-able objects
type Constructor<T> = (...args: any[]) => T;
type ClassWithConstructor<T> = { new (...args: any[]): T };

/**
* Checks whether given value's type is an instance of provided constructor.
* {@link isInstanceOf}.
Expand All @@ -148,7 +154,7 @@ export function isSyntheticEvent(wat: any): boolean {
* @param base A constructor to be used in a check.
* @returns A boolean representing the result.
*/
export function isInstanceOf(wat: any, base: any): boolean {
export function isInstanceOf<T>(wat: any, base: Constructor<T> | ClassWithConstructor<T>): wat is T {
try {
return wat instanceof base;
} catch (_e) {
Expand Down
13 changes: 6 additions & 7 deletions packages/utils/src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function getWalkSource(
currentTarget?: unknown;
}

const event = value as SimpleEvent;
const event = (value as unknown) as SimpleEvent;

const source: {
[key: string]: any;
Expand Down Expand Up @@ -381,9 +381,8 @@ export function extractExceptionKeysForMessage(exception: any, maxLength: number
* Given any object, return the new object with removed keys that value was `undefined`.
* Works recursively on objects and arrays.
*/
export function dropUndefinedKeys<T>(val: T): T {
if (isPlainObject(val)) {
const obj = val as { [key: string]: any };
export function dropUndefinedKeys<T>(obj: T): T {
if (isPlainObject(obj)) {
const rv: { [key: string]: any } = {};
for (const key of Object.keys(obj)) {
if (typeof obj[key] !== 'undefined') {
Expand All @@ -393,11 +392,11 @@ export function dropUndefinedKeys<T>(val: T): T {
return rv as T;
}

if (Array.isArray(val)) {
return (val as any[]).map(dropUndefinedKeys) as any;
if (Array.isArray(obj)) {
return (obj as any[]).map(dropUndefinedKeys) as any;
}

return val;
return obj;
}

/**
Expand Down