From b0e8395f06ffd60d1f2be23fe31d4e9114f2de76 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 25 Nov 2024 17:26:38 +0100 Subject: [PATCH 1/4] ref: Refactor some `any` --- packages/browser-utils/src/instrument/dom.ts | 32 ++++++----------- .../src/integrations/reportingobserver.ts | 20 +++++++---- packages/cloudflare/src/handler.ts | 36 ++++++++++++------- packages/core/src/metrics/aggregator.ts | 8 ++--- packages/core/src/transports/base.ts | 3 +- packages/core/src/utils-hoist/isBrowser.ts | 6 ++-- packages/core/src/utils-hoist/misc.ts | 9 +++-- packages/core/src/utils-hoist/object.ts | 3 +- .../src/gcpfunction/http.ts | 3 +- .../pagesRouterRoutingInstrumentation.ts | 5 +-- .../nextjs/src/common/utils/tracingUtils.ts | 4 +-- .../serverComponentWrapperTemplate.ts | 6 ++-- packages/node/src/integrations/context.ts | 3 +- packages/node/src/proxy/base.ts | 3 +- packages/react/src/reactrouter.tsx | 2 +- 15 files changed, 74 insertions(+), 69 deletions(-) diff --git a/packages/browser-utils/src/instrument/dom.ts b/packages/browser-utils/src/instrument/dom.ts index 0da1ec4052e2..898ac9e1e240 100644 --- a/packages/browser-utils/src/instrument/dom.ts +++ b/packages/browser-utils/src/instrument/dom.ts @@ -64,24 +64,20 @@ export function instrumentDOM(): void { // could potentially prevent the event from bubbling up to our global listeners. This way, our handler are still // guaranteed to fire at least once.) ['EventTarget', 'Node'].forEach((target: string) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - const proto = (WINDOW as any)[target] && (WINDOW as any)[target].prototype; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins - if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) { + const globalObject = WINDOW as unknown as Record; + const targetObj = globalObject[target]; + const proto = targetObj && targetObj.prototype; + + if (!proto || Object.prototype.hasOwnProperty.call(proto, 'addEventListener')) { return; } fill(proto, 'addEventListener', function (originalAddEventListener: AddEventListener): AddEventListener { - return function ( - this: Element, - type: string, - listener: EventListenerOrEventListenerObject, - options?: boolean | AddEventListenerOptions, - ): AddEventListener { + return function (this: InstrumentedElement, type, listener, options): AddEventListener { if (type === 'click' || type == 'keypress') { try { - const el = this as InstrumentedElement; - const handlers = (el.__sentry_instrumentation_handlers__ = el.__sentry_instrumentation_handlers__ || {}); + const handlers = (this.__sentry_instrumentation_handlers__ = + this.__sentry_instrumentation_handlers__ || {}); const handlerForType = (handlers[type] = handlers[type] || { refCount: 0 }); if (!handlerForType.handler) { @@ -105,16 +101,10 @@ export function instrumentDOM(): void { proto, 'removeEventListener', function (originalRemoveEventListener: RemoveEventListener): RemoveEventListener { - return function ( - this: Element, - type: string, - listener: EventListenerOrEventListenerObject, - options?: boolean | EventListenerOptions, - ): () => void { + return function (this: InstrumentedElement, type, listener, options): () => void { if (type === 'click' || type == 'keypress') { try { - const el = this as InstrumentedElement; - const handlers = el.__sentry_instrumentation_handlers__ || {}; + const handlers = this.__sentry_instrumentation_handlers__ || {}; const handlerForType = handlers[type]; if (handlerForType) { @@ -128,7 +118,7 @@ export function instrumentDOM(): void { // If there are no longer any custom handlers of any type on this element, cleanup everything. if (Object.keys(handlers).length === 0) { - delete el.__sentry_instrumentation_handlers__; + delete this.__sentry_instrumentation_handlers__; } } } catch (e) { diff --git a/packages/browser/src/integrations/reportingobserver.ts b/packages/browser/src/integrations/reportingobserver.ts index ee12f5adc5c9..022c12cf7abb 100644 --- a/packages/browser/src/integrations/reportingobserver.ts +++ b/packages/browser/src/integrations/reportingobserver.ts @@ -46,6 +46,13 @@ interface ReportingObserverOptions { types?: ReportTypes[]; } +/** This is experimental and the types are not included with TypeScript, sadly. */ +interface ReportingObserverClass { + new (handler: (reports: Report[]) => void, options: { buffered?: boolean; types?: ReportTypes[] }): { + observe: () => void; + }; +} + const SETUP_CLIENTS = new WeakMap(); const _reportingObserverIntegration = ((options: ReportingObserverOptions = {}) => { @@ -99,13 +106,14 @@ const _reportingObserverIntegration = ((options: ReportingObserverOptions = {}) return; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - const observer = new (WINDOW as any).ReportingObserver(handler, { - buffered: true, - types, - }); + const observer = new (WINDOW as typeof WINDOW & { ReportingObserver: ReportingObserverClass }).ReportingObserver( + handler, + { + buffered: true, + types, + }, + ); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access observer.observe(); }, diff --git a/packages/cloudflare/src/handler.ts b/packages/cloudflare/src/handler.ts index 51260f01d755..42f41e40a13e 100644 --- a/packages/cloudflare/src/handler.ts +++ b/packages/cloudflare/src/handler.ts @@ -40,8 +40,7 @@ export function withSentry>( ): E { setAsyncLocalStorageAsyncContextStrategy(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - if ('fetch' in handler && typeof handler.fetch === 'function' && !(handler.fetch as any).__SENTRY_INSTRUMENTED__) { + if ('fetch' in handler && typeof handler.fetch === 'function' && !isInstrumented(handler.fetch)) { handler.fetch = new Proxy(handler.fetch, { apply(target, thisArg, args: Parameters>>) { const [request, env, context] = args; @@ -50,16 +49,10 @@ export function withSentry>( }, }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - (handler.fetch as any).__SENTRY_INSTRUMENTED__ = true; + markAsInstrumented(handler.fetch); } - if ( - 'scheduled' in handler && - typeof handler.scheduled === 'function' && - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - !(handler.scheduled as any).__SENTRY_INSTRUMENTED__ - ) { + if ('scheduled' in handler && typeof handler.scheduled === 'function' && !isInstrumented(handler.scheduled)) { handler.scheduled = new Proxy(handler.scheduled, { apply(target, thisArg, args: Parameters>>) { const [event, env, context] = args; @@ -97,9 +90,28 @@ export function withSentry>( }, }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - (handler.scheduled as any).__SENTRY_INSTRUMENTED__ = true; + markAsInstrumented(handler.scheduled); } return handler; } + +type SentryInstrumented = T & { + __SENTRY_INSTRUMENTED__?: boolean; +}; + +function markAsInstrumented(handler: T): void { + try { + (handler as SentryInstrumented).__SENTRY_INSTRUMENTED__ = true; + } catch { + // ignore errors here + } +} + +function isInstrumented(handler: T): boolean { + try { + return !!(handler as SentryInstrumented).__SENTRY_INSTRUMENTED__; + } catch { + return false; + } +} diff --git a/packages/core/src/metrics/aggregator.ts b/packages/core/src/metrics/aggregator.ts index 899597e30d06..e2a14f62eb9e 100644 --- a/packages/core/src/metrics/aggregator.ts +++ b/packages/core/src/metrics/aggregator.ts @@ -21,8 +21,7 @@ export class MetricsAggregator implements MetricsAggregatorBase { private _bucketsTotalWeight; // Cast to any so that it can use Node.js timeout - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readonly _interval: any; + private readonly _interval: ReturnType; // SDKs are required to shift the flush interval by random() * rollup_in_seconds. // That shift is determined once per startup to create jittering. @@ -40,11 +39,8 @@ export class MetricsAggregator implements MetricsAggregatorBase { this._buckets = new Map(); this._bucketsTotalWeight = 0; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this._interval = setInterval(() => this._flush(), DEFAULT_FLUSH_INTERVAL) as any; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + this._interval = setInterval(() => this._flush(), DEFAULT_FLUSH_INTERVAL); if (this._interval.unref) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access this._interval.unref(); } diff --git a/packages/core/src/transports/base.ts b/packages/core/src/transports/base.ts index e9c5753c3147..5303e43e6adf 100644 --- a/packages/core/src/transports/base.ts +++ b/packages/core/src/transports/base.ts @@ -61,8 +61,7 @@ export function createTransport( return resolvedSyncPromise({}); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const filteredEnvelope: Envelope = createEnvelope(envelope[0], filteredEnvelopeItems as any); + const filteredEnvelope: Envelope = createEnvelope(envelope[0], filteredEnvelopeItems as (typeof envelope)[1]); // Creates client report for each item in an envelope const recordEnvelopeLoss = (reason: EventDropReason): void => { diff --git a/packages/core/src/utils-hoist/isBrowser.ts b/packages/core/src/utils-hoist/isBrowser.ts index 1670d347f4bb..b77d65c0f3ff 100644 --- a/packages/core/src/utils-hoist/isBrowser.ts +++ b/packages/core/src/utils-hoist/isBrowser.ts @@ -13,8 +13,6 @@ type ElectronProcess = { type?: string }; // Electron renderers with nodeIntegration enabled are detected as Node.js so we specifically test for them function isElectronNodeRenderer(): boolean { - return ( - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - (GLOBAL_OBJ as any).process !== undefined && ((GLOBAL_OBJ as any).process as ElectronProcess).type === 'renderer' - ); + const process = (GLOBAL_OBJ as typeof GLOBAL_OBJ & { process?: ElectronProcess }).process; + return !!process && process.type === 'renderer'; } diff --git a/packages/core/src/utils-hoist/misc.ts b/packages/core/src/utils-hoist/misc.ts index 8f1cfe371ece..5a3190e0f87b 100644 --- a/packages/core/src/utils-hoist/misc.ts +++ b/packages/core/src/utils-hoist/misc.ts @@ -211,8 +211,7 @@ export function addContextToFrame(lines: string[], frame: StackFrame, linesOfCon * @returns `true` if the exception has already been captured, `false` if not (with the side effect of marking it seen) */ export function checkOrSetAlreadyCaught(exception: unknown): boolean { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (exception && (exception as any).__sentry_captured__) { + if (isAlreadyCaptured(exception)) { return true; } @@ -227,6 +226,12 @@ export function checkOrSetAlreadyCaught(exception: unknown): boolean { return false; } +function isAlreadyCaptured(exception: unknown): boolean | void { + try { + return (exception as { __sentry_captured__?: boolean }).__sentry_captured__; + } catch {} // eslint-disable-line no-empty +} + /** * Checks whether the given input is already an array, and if it isn't, wraps it in one. * diff --git a/packages/core/src/utils-hoist/object.ts b/packages/core/src/utils-hoist/object.ts index 1c68fd8c51e7..f5703feaa1da 100644 --- a/packages/core/src/utils-hoist/object.ts +++ b/packages/core/src/utils-hoist/object.ts @@ -296,7 +296,8 @@ function isPojo(input: unknown): input is Record { export function objectify(wat: unknown): typeof Object { let objectified; switch (true) { - case wat === undefined || wat === null: + // this will catch both undefined and null + case wat == undefined: objectified = new String(wat); break; diff --git a/packages/google-cloud-serverless/src/gcpfunction/http.ts b/packages/google-cloud-serverless/src/gcpfunction/http.ts index 46b61c0ee8f1..3679ef61e173 100644 --- a/packages/google-cloud-serverless/src/gcpfunction/http.ts +++ b/packages/google-cloud-serverless/src/gcpfunction/http.ts @@ -27,8 +27,7 @@ export function wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial; * first time. */ export function escapeNextjsTracing(cb: () => T): T { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - const MaybeGlobalAsyncLocalStorage = (GLOBAL_OBJ as any).AsyncLocalStorage; + const MaybeGlobalAsyncLocalStorage = (GLOBAL_OBJ as { AsyncLocalStorage?: new () => AsyncLocalStorage }) + .AsyncLocalStorage; if (!MaybeGlobalAsyncLocalStorage) { DEBUG_BUILD && diff --git a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts index d8d8e26f464a..f4223d752d47 100644 --- a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts @@ -41,14 +41,13 @@ if (typeof serverComponent === 'function') { // Current assumption is that Next.js applies some loader magic to userfiles, but not files in node_modules. This file // is technically a userfile so it gets the loader magic applied. wrappedServerComponent = new Proxy(serverComponent, { - apply: (originalFunction, thisArg, args) => { + apply: (originalFunction: (...args: unknown[]) => unknown, thisArg, args) => { let sentryTraceHeader: string | undefined | null = undefined; let baggageHeader: string | undefined | null = undefined; let headers: WebFetchHeaders | undefined = undefined; // We try-catch here just in `requestAsyncStorage` is undefined since it may not be defined try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const requestAsyncStore = requestAsyncStorage?.getStore() as ReturnType; sentryTraceHeader = requestAsyncStore?.headers.get('sentry-trace') ?? undefined; baggageHeader = requestAsyncStore?.headers.get('baggage') ?? undefined; @@ -57,8 +56,7 @@ if (typeof serverComponent === 'function') { /** empty */ } - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - return Sentry.wrapServerComponentWithSentry(originalFunction as any, { + return Sentry.wrapServerComponentWithSentry(originalFunction, { componentRoute: '__ROUTE__', componentType: '__COMPONENT_TYPE__', sentryTraceHeader, diff --git a/packages/node/src/integrations/context.ts b/packages/node/src/integrations/context.ts index a3d45a5cc6c9..ec74064a5dff 100644 --- a/packages/node/src/integrations/context.ts +++ b/packages/node/src/integrations/context.ts @@ -171,8 +171,7 @@ async function getOsContext(): Promise { function getCultureContext(): CultureContext | undefined { try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - if (typeof (process.versions as unknown as any).icu !== 'string') { + if (typeof process.versions.icu !== 'string') { // Node was built without ICU support return; } diff --git a/packages/node/src/proxy/base.ts b/packages/node/src/proxy/base.ts index 6e374160b16a..d758abf2b224 100644 --- a/packages/node/src/proxy/base.ts +++ b/packages/node/src/proxy/base.ts @@ -81,8 +81,7 @@ export abstract class Agent extends http.Agent { if (options) { // First check the `secureEndpoint` property explicitly, since this // means that a parent `Agent` is "passing through" to this instance. - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - if (typeof (options as any).secureEndpoint === 'boolean') { + if (typeof (options as Partial).secureEndpoint === 'boolean') { return options.secureEndpoint; } diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index eb72949c23b9..9f02f69cff06 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -223,7 +223,7 @@ function computeRootMatch(pathname: string): Match { /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */ export function withSentryRouting

, R extends React.ComponentType

>(Route: R): R { - const componentDisplayName = (Route as any).displayName || (Route as any).name; + const componentDisplayName = Route.displayName || Route.name; const WrappedRoute: React.FC

= (props: P) => { if (props && props.computedMatch && props.computedMatch.isExact) { From 56e572065859fcaee2ef7eafed4470c8b1f7e6b8 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 26 Nov 2024 13:03:03 +0100 Subject: [PATCH 2/4] fixes --- packages/browser-utils/src/instrument/dom.ts | 3 ++- packages/core/src/metrics/aggregator.ts | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/browser-utils/src/instrument/dom.ts b/packages/browser-utils/src/instrument/dom.ts index 898ac9e1e240..d94a84360e52 100644 --- a/packages/browser-utils/src/instrument/dom.ts +++ b/packages/browser-utils/src/instrument/dom.ts @@ -68,7 +68,8 @@ export function instrumentDOM(): void { const targetObj = globalObject[target]; const proto = targetObj && targetObj.prototype; - if (!proto || Object.prototype.hasOwnProperty.call(proto, 'addEventListener')) { + // eslint-disable-next-line no-prototype-builtins + if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) { return; } diff --git a/packages/core/src/metrics/aggregator.ts b/packages/core/src/metrics/aggregator.ts index e2a14f62eb9e..48a2bed74a9a 100644 --- a/packages/core/src/metrics/aggregator.ts +++ b/packages/core/src/metrics/aggregator.ts @@ -20,8 +20,7 @@ export class MetricsAggregator implements MetricsAggregatorBase { // that we store in memory. private _bucketsTotalWeight; - // Cast to any so that it can use Node.js timeout - private readonly _interval: ReturnType; + private readonly _interval: ReturnType & { unref?: () => void }; // SDKs are required to shift the flush interval by random() * rollup_in_seconds. // That shift is determined once per startup to create jittering. From 677ac317e7243aafd51bb50578d1ad2b715b101f Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 29 Nov 2024 11:36:51 +0100 Subject: [PATCH 3/4] adjustments --- packages/cloudflare/src/handler.ts | 4 ++-- packages/core/src/metrics/aggregator.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cloudflare/src/handler.ts b/packages/cloudflare/src/handler.ts index 42f41e40a13e..06cb13bb8b05 100644 --- a/packages/cloudflare/src/handler.ts +++ b/packages/cloudflare/src/handler.ts @@ -108,9 +108,9 @@ function markAsInstrumented(handler: T): void { } } -function isInstrumented(handler: T): boolean { +function isInstrumented(handler: T): boolean | undefined { try { - return !!(handler as SentryInstrumented).__SENTRY_INSTRUMENTED__; + return (handler as SentryInstrumented).__SENTRY_INSTRUMENTED__; } catch { return false; } diff --git a/packages/core/src/metrics/aggregator.ts b/packages/core/src/metrics/aggregator.ts index 48a2bed74a9a..b3213ef87f0c 100644 --- a/packages/core/src/metrics/aggregator.ts +++ b/packages/core/src/metrics/aggregator.ts @@ -20,6 +20,7 @@ export class MetricsAggregator implements MetricsAggregatorBase { // that we store in memory. private _bucketsTotalWeight; + // We adjust the type here to add the `unref()` part, as setInterval can technically return a number of a NodeJS.Timer. private readonly _interval: ReturnType & { unref?: () => void }; // SDKs are required to shift the flush interval by random() * rollup_in_seconds. From 7353c1ac96391e6eb15a1a89152e65bc15845fae Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 29 Nov 2024 11:56:17 +0100 Subject: [PATCH 4/4] properly ignore solidstart build files for biome --- biome.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/biome.json b/biome.json index cee069ac3127..49b865345e39 100644 --- a/biome.json +++ b/biome.json @@ -44,6 +44,9 @@ "angular.json", "ember/instance-initializers/**", "ember/types.d.ts", + "solidstart/*.d.ts", + "solidstart/client/", + "solidstart/server/", ".output", ".vinxi" ]