Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion packages/cloudflare/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function getEnvVar<T extends Record<string, unknown>>(env: unknown, varName: key
* @returns The final options.
*/
export function getFinalOptions(userOptions: CloudflareOptions = {}, env: unknown): CloudflareOptions {
if (typeof env !== 'object' || env === null) {
if (!isObjectLike(env)) {
return userOptions;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/deno/src/opentelemetry/tracer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SENTRY_ORIGIN } from '@sentry/conventions/attributes';
import type { Context, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api';
import { trace } from '@opentelemetry/api';
import { startInactiveSpan, startSpanManual } from '@sentry/core';
import { isObjectLike, startInactiveSpan, startSpanManual } from '@sentry/core';

/**
* Set up a mock OTEL tracer to allow inter-op with OpenTelemetry emitted spans.
Expand Down Expand Up @@ -58,7 +58,7 @@ class SentryDenoTracer implements Tracer {
context?: unknown,
fn?: F,
): ReturnType<F> {
const opts = (typeof options === 'object' && options !== null ? options : {}) as SpanOptions;
const opts = (isObjectLike(options) ? options : {}) as SpanOptions;

const spanOpts = {
...opts,
Expand Down
17 changes: 8 additions & 9 deletions packages/nestjs/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isObjectLike } from '@sentry/core';

/**
* Determines if the exception is an expected NestJS control flow error.
* - HttpException have getStatus and getResponse methods: https://github.com/nestjs/nest/blob/master/packages/microservices/exceptions/rpc-exception.ts
Expand All @@ -10,17 +12,15 @@
* @returns `true` if the exception is expected and should not be reported to Sentry, otherwise `false`.
*/
export function isExpectedError(exception: unknown): boolean {
if (typeof exception !== 'object' || exception === null) {
if (!isObjectLike(exception)) {
return false;
}

const ex = exception as Record<string, unknown>;

// HttpException
if (
typeof ex.getStatus === 'function' &&
typeof ex.getResponse === 'function' &&
typeof ex.initMessage === 'function'
typeof exception.getStatus === 'function' &&
typeof exception.getResponse === 'function' &&
typeof exception.initMessage === 'function'
) {
return true;
}
Expand All @@ -39,10 +39,9 @@ export function isExpectedError(exception: unknown): boolean {
* We use duck-typing to avoid importing from `@nestjs/websockets` or `@nestjs/microservices`.
*/
export function isWsOrRpcException(exception: unknown): boolean {
if (typeof exception !== 'object' || exception === null) {
if (!isObjectLike(exception)) {
return false;
}

const ex = exception as Record<string, unknown>;
return typeof ex.getError === 'function' && typeof ex.initMessage === 'function';
return typeof exception.getError === 'function' && typeof exception.initMessage === 'function';
}
4 changes: 2 additions & 2 deletions packages/nestjs/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
} from '@nestjs/common';
import { Catch, Global, HttpException, Injectable, Logger, Module } from '@nestjs/common';
import { APP_INTERCEPTOR, BaseExceptionFilter } from '@nestjs/core';
import { captureException, debug, getDefaultIsolationScope, getIsolationScope } from '@sentry/core';
import { captureException, debug, getDefaultIsolationScope, getIsolationScope, isObjectLike } from '@sentry/core';
import type { Observable } from 'rxjs';
import { isExpectedError, isWsOrRpcException } from './helpers';

Expand Down Expand Up @@ -166,7 +166,7 @@ class SentryGlobalFilter extends BaseExceptionFilter {

if (isWsOrRpcException(exception)) {
const result = (exception as { getError: () => unknown }).getError();
const response = typeof result === 'object' && result !== null ? result : { status: 'error', message: result };
const response = isObjectLike(result) ? result : { status: 'error', message: result };
client.emit?.('exception', response);
return;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/nestjs/test/sentry-global-filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ vi.mock('../src/helpers', () => ({
isWsOrRpcException: vi.fn(),
}));

vi.mock('@sentry/core', () => ({
vi.mock('@sentry/core', async importOriginal => ({
isObjectLike: (await importOriginal<typeof SentryCore>()).isObjectLike,
captureException: vi.fn().mockReturnValue('mock-event-id'),
getIsolationScope: vi.fn(),
getDefaultIsolationScope: vi.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Client, Span } from '@sentry/core';
import {
GLOBAL_OBJ,
hasSpanStreamingEnabled,
isObjectLike,
NAVIGATION_SPAN_NAME_FALLBACK,
PAGELOAD_SPAN_NAME_FALLBACK,
filterCollectedUrl,
Expand Down Expand Up @@ -250,8 +251,8 @@ export function appRouterInstrumentNavigation(client: Client): void {
if (globalValue) {
GLOBAL_OBJ_WITH_NEXT_ROUTER.next = new Proxy(globalValue, {
set(target, p, newValue) {
if (p === 'router' && typeof newValue === 'object' && newValue !== null) {
patchRouter(client, newValue, currentRouterPatchingNavigationSpanRef);
if (p === 'router' && isObjectLike(newValue)) {
patchRouter(client, newValue as unknown as NextRouter, currentRouterPatchingNavigationSpanRef);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uncommented double cast on router

Low Severity

A new as unknown as NextRouter double-cast was added without a comment explaining why a safer typing is not possible. I flagged this because the review rules require every new double-cast in SDK source to include that justification.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 42b71f5. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was any before, we keep the assumption that Next assign a NextRouter here.

}

// @ts-expect-error we cannot possibly type this
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isObjectLike } from '@sentry/core';
import type App from 'next/app';
import { isBuild } from '../utils/isBuild';
import { withErrorInstrumentation, withTracedServerSideDataFetcher } from '../utils/wrapperUtils';
Expand Down Expand Up @@ -45,23 +46,23 @@ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetI
baggage?: string;
} = await tracedGetInitialProps.apply(thisArg, args);

if (typeof appGetInitialProps === 'object' && appGetInitialProps !== null) {
if (isObjectLike(appGetInitialProps)) {
// Per definition, `pageProps` is not optional, however an increased amount of users doesn't seem to call
// `App.getInitialProps(appContext)` in their custom `_app` pages which is required as per
// https://nextjs.org/docs/advanced-features/custom-app - resulting in missing `pageProps`.
// For this reason, we just handle the case where `pageProps` doesn't exist explicitly.
if (!(appGetInitialProps as Record<string, unknown>).pageProps) {
(appGetInitialProps as Record<string, unknown>).pageProps = {};
if (!appGetInitialProps.pageProps) {
appGetInitialProps.pageProps = {};
}

// The Next.js serializer throws on undefined values so we need to guard for it (#12102)
if (sentryTrace) {
(appGetInitialProps as { pageProps: Record<string, unknown> }).pageProps._sentryTraceData = sentryTrace;
(appGetInitialProps.pageProps as Record<string, unknown>)._sentryTraceData = sentryTrace;
}

// The Next.js serializer throws on undefined values so we need to guard for it (#12102)
if (baggage) {
(appGetInitialProps as { pageProps: Record<string, unknown> }).pageProps._sentryBaggage = baggage;
(appGetInitialProps.pageProps as Record<string, unknown>)._sentryBaggage = baggage;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isObjectLike } from '@sentry/core';
import type { NextPageContext } from 'next';
import type { ErrorProps } from 'next/error';
import { isBuild } from '../utils/isBuild';
Expand Down Expand Up @@ -47,15 +48,15 @@ export function wrapErrorGetInitialPropsWithSentry(
sentryTrace?: string;
} = await tracedGetInitialProps.apply(thisArg, args);

if (typeof errorGetInitialProps === 'object' && errorGetInitialProps !== null) {
if (isObjectLike(errorGetInitialProps)) {
if (sentryTrace) {
// The Next.js serializer throws on undefined values so we need to guard for it (#12102)
(errorGetInitialProps as Record<string, unknown>)._sentryTraceData = sentryTrace;
errorGetInitialProps._sentryTraceData = sentryTrace;
}

// The Next.js serializer throws on undefined values so we need to guard for it (#12102)
if (baggage) {
(errorGetInitialProps as Record<string, unknown>)._sentryBaggage = baggage;
errorGetInitialProps._sentryBaggage = baggage;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isObjectLike } from '@sentry/core';
import type { NextPage } from 'next';
import { isBuild } from '../utils/isBuild';
import { withErrorInstrumentation, withTracedServerSideDataFetcher } from '../utils/wrapperUtils';
Expand Down Expand Up @@ -43,15 +44,15 @@ export function wrapGetInitialPropsWithSentry(origGetInitialProps: GetInitialPro
sentryTrace?: string;
} = (await tracedGetInitialProps.apply(thisArg, args)) ?? {}; // Next.js allows undefined to be returned from a getInitialPropsFunction.

if (typeof initialProps === 'object' && initialProps !== null) {
if (isObjectLike(initialProps)) {
// The Next.js serializer throws on undefined values so we need to guard for it (#12102)
if (sentryTrace) {
(initialProps as Record<string, unknown>)._sentryTraceData = sentryTrace;
initialProps._sentryTraceData = sentryTrace;
}

// The Next.js serializer throws on undefined values so we need to guard for it (#12102)
if (baggage) {
(initialProps as Record<string, unknown>)._sentryBaggage = baggage;
initialProps._sentryBaggage = baggage;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isObjectLike } from '@sentry/core';
import type { GetServerSideProps } from 'next';
import { isBuild } from '../utils/isBuild';
import { withErrorInstrumentation, withTracedServerSideDataFetcher } from '../utils/wrapperUtils';
Expand Down Expand Up @@ -39,7 +40,7 @@ export function wrapGetServerSidePropsWithSentry(
sentryTrace?: string;
} = await (tracedGetServerSideProps.apply(thisArg, args) as ReturnType<typeof tracedGetServerSideProps>);

if (typeof serverSideProps === 'object' && serverSideProps !== null && 'props' in serverSideProps) {
if (isObjectLike(serverSideProps) && 'props' in serverSideProps) {
// The Next.js serializer throws on undefined values so we need to guard for it (#12102)
if (sentryTrace) {
(serverSideProps.props as Record<string, unknown>)._sentryTraceData = sentryTrace;
Expand Down
9 changes: 5 additions & 4 deletions packages/react-router/src/client/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getClient,
GLOBAL_OBJ,
hasSpanStreamingEnabled,
isObjectLike,
NAVIGATION_SPAN_NAME_FALLBACK,
filterCollectedUrl,
} from '@sentry/core';
Expand All @@ -24,13 +25,13 @@ const GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
* to avoid "[object Object]" transaction names.
*/
export function resolveNavigateArg(target: unknown): string {
if (typeof target !== 'object' || target === null) {
if (!isObjectLike(target)) {
// string or number
return String(target);
}

// Object `to` with pathname
const pathname = (target as Record<string, unknown>).pathname;
const pathname = target.pathname;
if (typeof pathname === 'string') {
return pathname || '/';
}
Expand All @@ -46,12 +47,12 @@ export function resolveNavigateArg(target: unknown): string {
* destination the user is navigating to, including any query string.
*/
export function resolveNavigateUrl(target: unknown): string {
if (typeof target !== 'object' || target === null) {
if (!isObjectLike(target)) {
// string or number
return String(target);
}

const { pathname, search, hash } = target as Record<string, unknown>;
const { pathname, search, hash } = target;
const path = typeof pathname === 'string' && pathname !== '' ? pathname : WINDOW.location?.pathname || '/';

return `${path}${typeof search === 'string' ? search : ''}${typeof hash === 'string' ? hash : ''}`;
Expand Down
6 changes: 3 additions & 3 deletions packages/remix/src/v3/server/errorFilter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { captureException } from '@sentry/core';
import { captureException, isObjectLike } from '@sentry/core';

/** Decides whether a thrown value should become a Sentry issue. */
export type ShouldHandleError = (error: unknown) => boolean;
Expand All @@ -8,11 +8,11 @@ export type ShouldHandleError = (error: unknown) => boolean;
* outcome, not a fault, and the request still produces a span.
*/
export function defaultShouldHandleError(error: unknown): boolean {
if (typeof error !== 'object' || error === null) {
if (!isObjectLike(error)) {
return true;
}

const status = (error as { status?: unknown }).status;
const status = error.status;

return !(typeof status === 'number' && status >= 300 && status < 500);
}
Expand Down
6 changes: 5 additions & 1 deletion packages/remix/test/v3/errorFilter.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type * as SentryCore from '@sentry/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const captureException = vi.fn();
vi.mock('@sentry/core', () => ({ captureException: (...args: unknown[]) => captureException(...args) }));
vi.mock('@sentry/core', async importOriginal => ({
isObjectLike: (await importOriginal<typeof SentryCore>()).isObjectLike,
captureException: (...args: unknown[]) => captureException(...args),
}));

const { captureRequestError, defaultShouldHandleError, isRequestAbort, setShouldHandleError } =
await import('../../src/v3/server/errorFilter');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isObjectLike } from '@sentry/core';

/**
* Default implementation of the `shouldHandleError` callback.
*
Expand All @@ -9,11 +11,11 @@
* Hono's `HTTPException`, third-party middleware errors, and custom error subclasses.
*/
export function defaultShouldHandleError(error: unknown): boolean {
if (typeof error !== 'object' || error === null) {
if (!isObjectLike(error)) {
return true;
}

const status = (error as { status?: unknown }).status;
const status = error.status;

return !(typeof status === 'number' && status >= 300 && status < 500);
}
3 changes: 2 additions & 1 deletion packages/server-utils/src/integrations/kafkajs/spans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
getClient,
getTraceData,
hasSpanStreamingEnabled,
isObjectLike,
propagationContextFromHeaders,
SPAN_STATUS_ERROR,
startInactiveSpan,
Expand Down Expand Up @@ -183,7 +184,7 @@ export function applyErrorToSpans(spans: Span[], reason: unknown): void {
let errorType: string = ERROR_TYPE_VALUE_OTHER;
if (typeof reason === 'string' || reason === undefined) {
errorMessage = reason;
} else if (typeof reason === 'object' && reason !== null && Object.prototype.hasOwnProperty.call(reason, 'message')) {
} else if (isObjectLike(reason) && Object.prototype.hasOwnProperty.call(reason, 'message')) {
errorMessage = (reason as { message?: string }).message;
errorType = (reason as { constructor: { name: string } }).constructor.name;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/server-utils/src/integrations/langgraph.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as diagnosticsChannel from '../utils/diagnosticsChannel';
import type { IntegrationFn } from '@sentry/core';
import { debug, defineIntegration } from '@sentry/core';
import { debug, defineIntegration, isObjectLike } from '@sentry/core';
import { resolveAIRecordingOptions } from '../ai/core/utils';
import { createLangChainCallbackHandler } from '../ai/langchain';
import { instrumentCompiledGraphInvoke } from '../ai/langgraph';
Expand Down Expand Up @@ -95,7 +95,7 @@ function instrumentLanggraph(options: LangGraphOptions): void {
function getFirstArgObject(args: unknown[] | undefined): Record<string, unknown> | undefined {
const first = (args ?? [])[0];

return typeof first === 'object' && first !== null ? (first as Record<string, unknown>) : undefined;
return isObjectLike(first) ? first : undefined;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/server-utils/src/integrations/mastra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ function instrumentExecuteWithContext(): void {
}

function attachExporter(instance: unknown, options: MastraOptions): void {
if (typeof instance !== 'object' || instance === null || registered.has(instance)) {
if (!isObjectLike(instance) || registered.has(instance)) {
return;
}

Expand Down
Loading