From 859e83182470b2c221c2744d630e74a6955a3e25 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Mon, 2 Feb 2026 12:05:08 -0500 Subject: [PATCH 1/5] chore: initial config and rule setup --- docs/oxlint-migration-gaps.md | 145 ++++++++++++++++++++++++++++++++++ nx.json | 2 +- 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 docs/oxlint-migration-gaps.md diff --git a/docs/oxlint-migration-gaps.md b/docs/oxlint-migration-gaps.md new file mode 100644 index 000000000000..7893e4c94261 --- /dev/null +++ b/docs/oxlint-migration-gaps.md @@ -0,0 +1,145 @@ +# Oxlint Migration Gaps + +This document tracks the ESLint rules that are not natively supported in Oxlint, or have different behavior. + +## Migration Summary + +| Metric | Value | +| ----------------------- | ----------------------------------------- | +| ESLint Version | 8.57.0 | +| Oxlint Version | 1.43.0 | +| Performance Improvement | ~330x faster (24ms vs ~8s for same files) | +| Total Files Linted | 1953 | +| Rules Active | 93+ | + +## Unsupported ESLint Plugins/Rules + +### @typescript-eslint Rules Not Supported + +| Rule | Status | Notes | +| -------------------------------------------------- | ---------- | ------------------------------------------ | +| `@typescript-eslint/no-inferrable-types` | Not needed | Was disabled in ESLint config | +| `@typescript-eslint/typedef` | **Gap** | Enforces type annotations on variables | +| `@typescript-eslint/member-ordering` | **Gap** | Class member ordering enforcement | +| `@typescript-eslint/naming-convention` | **Gap** | Private/protected member underscore prefix | +| `@typescript-eslint/unified-signatures` | **Gap** | Prevent unnecessary overloads | +| `@typescript-eslint/explicit-member-accessibility` | **Gap** | public/private/protected keywords | + +### eslint-plugin-deprecation + +| Rule | Status | Notes | +| ------------------------- | ------- | --------------------------------------------------- | +| `deprecation/deprecation` | Partial | Use `typescript/no-deprecated` with type-aware mode | + +### eslint-plugin-import + +| Rule | Status | Notes | +| ----------------------------------- | ------- | --------------------------------- | +| `import/no-extraneous-dependencies` | **Gap** | Check for undeclared dependencies | +| `import/first` | **Gap** | Imports should come first | +| `import/newline-after-import` | **Gap** | Newline after import block | + +### eslint-plugin-simple-import-sort + +| Rule | Status | Notes | +| ---------------------------- | ------- | -------------------------------------------------- | +| `simple-import-sort/imports` | **Gap** | Import sorting - consider using Prettier or dprint | + +### eslint-plugin-jsdoc + +| Rule | Status | Notes | +| --------------------- | ------- | ----------------------------- | +| `jsdoc/require-jsdoc` | **Gap** | Require JSDoc for public APIs | + +### ESLint Core Rules + +| Rule | Status | Notes | +| ----------------------- | ------- | ------------------------------------------- | +| `max-lines` | **Gap** | File size limit (300 lines) | +| `spaced-comment` | **Gap** | Whitespace in comments | +| `no-restricted-globals` | **Gap** | Restrict window/document/location/navigator | + +## Custom Sentry Plugin Rules + +The `@sentry-internal/eslint-plugin-sdk` contains 6 custom rules. These can be loaded via Oxlint's JS plugins feature. + +| Rule | Status | Notes | +| ----------------------------- | ----------- | ----------------------------------------------- | +| `no-eq-empty` | **Gap** | Disallow `=== []` or `=== {}` | +| `no-class-field-initializers` | **Gap** | Disallow class field initializers (bundle size) | +| `no-regexp-constructor` | **Gap** | Warn about `new RegExp()` usage | +| `no-unsafe-random-apis` | **Gap** | Disallow `Math.random()` etc | +| `no-focused-tests` | **Covered** | Use `jest/no-focused-tests` | +| `no-skipped-tests` | **Covered** | Use `jest/no-disabled-tests` | + +## Type-Aware Linting + +Type-aware rules require the `--type-aware` flag and `oxlint-tsgolint` package: + +```bash +# Install type-aware package +yarn add -D oxlint-tsgolint + +# Run with type-aware rules +yarn lint:oxlint:type-aware +``` + +Type-aware mode enables additional checks like: + +- `typescript/no-floating-promises` (enhanced) +- `typescript/no-unsafe-member-access` (enhanced) +- `typescript/unbound-method` (enhanced) +- `typescript/no-deprecated` +- `typescript/no-base-to-string` +- `typescript/restrict-template-expressions` + +**Note**: Type-aware linting requires TypeScript 7+ and may need tsconfig adjustments. + +## Current Errors Found by Oxlint + +As of migration, Oxlint identifies the following issues that ESLint may not have caught: + +### Complexity Issues (21 functions) + +Functions exceeding cyclomatic complexity of 20: + +- `getNotificationAttributes` (31) +- `constructor` in replay integration (32) +- `xhrCallback` (27) +- `_INTERNAL_captureLog` (28) +- And others... + +### Unused Variables + +- Unused catch parameters not prefixed with `_` +- Unused function declarations + +### Code Quality + +- Bitwise operations (intentionally used in replay packages) +- Missing return types on some callback functions + +## Recommendations + +1. **JS Plugins**: Load the custom Sentry plugin via `jsPlugins` config option +2. **Prettier Integration**: Use Prettier for import sorting since `simple-import-sort` is not supported +3. **Type-Aware**: Enable type-aware linting in CI for enhanced TypeScript checks +4. **Fix Incrementally**: Address the 71+ errors found by Oxlint over time + +## Performance Comparison + +``` +ESLint (packages/core + packages/browser): + Time: ~8 seconds + +Oxlint (same files): + Time: 24ms + Speedup: ~330x +``` + +## References + +- [Oxlint Documentation](https://oxc.rs/docs/guide/usage/linter/) +- [Migrate from ESLint](https://oxc.rs/docs/guide/usage/linter/migrate-from-eslint.html) +- [Type-Aware Linting](https://oxc.rs/docs/guide/usage/linter/type-aware.html) +- [JS Plugins](https://oxc.rs/docs/guide/usage/linter/js-plugins.html) diff --git a/nx.json b/nx.json index 7cd807e089fb..5cc18211d58b 100644 --- a/nx.json +++ b/nx.json @@ -51,7 +51,7 @@ "inputs": ["default"], "dependsOn": ["^build:types", "build:types"], "outputs": [], - "cache": true + "cache": false }, "test:unit": { "dependsOn": ["build:types", "^build:types", "build:transpile", "^build:transpile"], From 8b462bf4a805ba69020a528f9781374f685e8b9f Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Mon, 2 Feb 2026 13:41:42 -0500 Subject: [PATCH 2/5] chore: cleanup eslint --- docs/oxlint-migration-gaps.md | 145 ---------------------------------- 1 file changed, 145 deletions(-) delete mode 100644 docs/oxlint-migration-gaps.md diff --git a/docs/oxlint-migration-gaps.md b/docs/oxlint-migration-gaps.md deleted file mode 100644 index 7893e4c94261..000000000000 --- a/docs/oxlint-migration-gaps.md +++ /dev/null @@ -1,145 +0,0 @@ -# Oxlint Migration Gaps - -This document tracks the ESLint rules that are not natively supported in Oxlint, or have different behavior. - -## Migration Summary - -| Metric | Value | -| ----------------------- | ----------------------------------------- | -| ESLint Version | 8.57.0 | -| Oxlint Version | 1.43.0 | -| Performance Improvement | ~330x faster (24ms vs ~8s for same files) | -| Total Files Linted | 1953 | -| Rules Active | 93+ | - -## Unsupported ESLint Plugins/Rules - -### @typescript-eslint Rules Not Supported - -| Rule | Status | Notes | -| -------------------------------------------------- | ---------- | ------------------------------------------ | -| `@typescript-eslint/no-inferrable-types` | Not needed | Was disabled in ESLint config | -| `@typescript-eslint/typedef` | **Gap** | Enforces type annotations on variables | -| `@typescript-eslint/member-ordering` | **Gap** | Class member ordering enforcement | -| `@typescript-eslint/naming-convention` | **Gap** | Private/protected member underscore prefix | -| `@typescript-eslint/unified-signatures` | **Gap** | Prevent unnecessary overloads | -| `@typescript-eslint/explicit-member-accessibility` | **Gap** | public/private/protected keywords | - -### eslint-plugin-deprecation - -| Rule | Status | Notes | -| ------------------------- | ------- | --------------------------------------------------- | -| `deprecation/deprecation` | Partial | Use `typescript/no-deprecated` with type-aware mode | - -### eslint-plugin-import - -| Rule | Status | Notes | -| ----------------------------------- | ------- | --------------------------------- | -| `import/no-extraneous-dependencies` | **Gap** | Check for undeclared dependencies | -| `import/first` | **Gap** | Imports should come first | -| `import/newline-after-import` | **Gap** | Newline after import block | - -### eslint-plugin-simple-import-sort - -| Rule | Status | Notes | -| ---------------------------- | ------- | -------------------------------------------------- | -| `simple-import-sort/imports` | **Gap** | Import sorting - consider using Prettier or dprint | - -### eslint-plugin-jsdoc - -| Rule | Status | Notes | -| --------------------- | ------- | ----------------------------- | -| `jsdoc/require-jsdoc` | **Gap** | Require JSDoc for public APIs | - -### ESLint Core Rules - -| Rule | Status | Notes | -| ----------------------- | ------- | ------------------------------------------- | -| `max-lines` | **Gap** | File size limit (300 lines) | -| `spaced-comment` | **Gap** | Whitespace in comments | -| `no-restricted-globals` | **Gap** | Restrict window/document/location/navigator | - -## Custom Sentry Plugin Rules - -The `@sentry-internal/eslint-plugin-sdk` contains 6 custom rules. These can be loaded via Oxlint's JS plugins feature. - -| Rule | Status | Notes | -| ----------------------------- | ----------- | ----------------------------------------------- | -| `no-eq-empty` | **Gap** | Disallow `=== []` or `=== {}` | -| `no-class-field-initializers` | **Gap** | Disallow class field initializers (bundle size) | -| `no-regexp-constructor` | **Gap** | Warn about `new RegExp()` usage | -| `no-unsafe-random-apis` | **Gap** | Disallow `Math.random()` etc | -| `no-focused-tests` | **Covered** | Use `jest/no-focused-tests` | -| `no-skipped-tests` | **Covered** | Use `jest/no-disabled-tests` | - -## Type-Aware Linting - -Type-aware rules require the `--type-aware` flag and `oxlint-tsgolint` package: - -```bash -# Install type-aware package -yarn add -D oxlint-tsgolint - -# Run with type-aware rules -yarn lint:oxlint:type-aware -``` - -Type-aware mode enables additional checks like: - -- `typescript/no-floating-promises` (enhanced) -- `typescript/no-unsafe-member-access` (enhanced) -- `typescript/unbound-method` (enhanced) -- `typescript/no-deprecated` -- `typescript/no-base-to-string` -- `typescript/restrict-template-expressions` - -**Note**: Type-aware linting requires TypeScript 7+ and may need tsconfig adjustments. - -## Current Errors Found by Oxlint - -As of migration, Oxlint identifies the following issues that ESLint may not have caught: - -### Complexity Issues (21 functions) - -Functions exceeding cyclomatic complexity of 20: - -- `getNotificationAttributes` (31) -- `constructor` in replay integration (32) -- `xhrCallback` (27) -- `_INTERNAL_captureLog` (28) -- And others... - -### Unused Variables - -- Unused catch parameters not prefixed with `_` -- Unused function declarations - -### Code Quality - -- Bitwise operations (intentionally used in replay packages) -- Missing return types on some callback functions - -## Recommendations - -1. **JS Plugins**: Load the custom Sentry plugin via `jsPlugins` config option -2. **Prettier Integration**: Use Prettier for import sorting since `simple-import-sort` is not supported -3. **Type-Aware**: Enable type-aware linting in CI for enhanced TypeScript checks -4. **Fix Incrementally**: Address the 71+ errors found by Oxlint over time - -## Performance Comparison - -``` -ESLint (packages/core + packages/browser): - Time: ~8 seconds - -Oxlint (same files): - Time: 24ms - Speedup: ~330x -``` - -## References - -- [Oxlint Documentation](https://oxc.rs/docs/guide/usage/linter/) -- [Migrate from ESLint](https://oxc.rs/docs/guide/usage/linter/migrate-from-eslint.html) -- [Type-Aware Linting](https://oxc.rs/docs/guide/usage/linter/type-aware.html) -- [JS Plugins](https://oxc.rs/docs/guide/usage/linter/js-plugins.html) From de18b0807d4366a7cfd71dfcc1c91da2bc8a5858 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 19 Feb 2026 11:24:21 -0500 Subject: [PATCH 3/5] chore: renable no floating promises --- .oxlintrc.json | 3 +-- packages/deno/src/utils/streaming.ts | 1 + .../react/src/reactrouter-compat-utils/instrumentation.tsx | 1 + packages/replay-internal/src/coreHandlers/handleHistory.ts | 1 + .../src/coreHandlers/util/addNetworkBreadcrumb.ts | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 6a11fcc33977..118e9815210a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -61,7 +61,7 @@ "typescript/consistent-type-imports": "error", "typescript/no-unnecessary-type-assertion": "error", "typescript/prefer-for-of": "error", - // "typescript/no-floating-promises": ["error", { "ignoreVoid": false }], + "typescript/no-floating-promises": ["error", { "ignoreVoid": true }], "typescript/no-dynamic-delete": "error", // "typescript/no-unsafe-member-access": "error", "typescript/unbound-method": "error", @@ -70,7 +70,6 @@ // === FIXME: Rules to turn back as error === "typescript/prefer-optional-chain": "warn", - "typescript/no-floating-promises": "warn", "typescript/no-unsafe-member-access": "warn" } }, diff --git a/packages/deno/src/utils/streaming.ts b/packages/deno/src/utils/streaming.ts index b999af39bf49..4f6f4654af68 100644 --- a/packages/deno/src/utils/streaming.ts +++ b/packages/deno/src/utils/streaming.ts @@ -81,6 +81,7 @@ function monitorStream( onDone: () => void, ): ReadableStream> { const reader = stream.getReader(); + // oxlint-disable-next-line typescript/no-floating-promises reader.closed.finally(() => onDone()); return new ReadableStream({ async start(controller) { diff --git a/packages/react/src/reactrouter-compat-utils/instrumentation.tsx b/packages/react/src/reactrouter-compat-utils/instrumentation.tsx index 5725309ff12b..e73438312676 100644 --- a/packages/react/src/reactrouter-compat-utils/instrumentation.tsx +++ b/packages/react/src/reactrouter-compat-utils/instrumentation.tsx @@ -249,6 +249,7 @@ function trackLazyRouteLoad(span: Span, promise: Promise): void { promises.add(promise); // Clean up when promise resolves/rejects + // oxlint-disable-next-line typescript/no-floating-promises promise.finally(() => { const currentPromises = pendingLazyRouteLoads.get(span); if (currentPromises) { diff --git a/packages/replay-internal/src/coreHandlers/handleHistory.ts b/packages/replay-internal/src/coreHandlers/handleHistory.ts index e12c65745914..aa85757e3a3e 100644 --- a/packages/replay-internal/src/coreHandlers/handleHistory.ts +++ b/packages/replay-internal/src/coreHandlers/handleHistory.ts @@ -38,6 +38,7 @@ export function handleHistorySpanListener(replay: ReplayContainer): (handlerData replay.triggerUserActivity(); replay.addUpdate(() => { + // oxlint-disable-next-line typescript/no-floating-promises createPerformanceSpans(replay, [result]); // Returning false to flush return false; diff --git a/packages/replay-internal/src/coreHandlers/util/addNetworkBreadcrumb.ts b/packages/replay-internal/src/coreHandlers/util/addNetworkBreadcrumb.ts index b67b27e6ab7f..38a19d1030f9 100644 --- a/packages/replay-internal/src/coreHandlers/util/addNetworkBreadcrumb.ts +++ b/packages/replay-internal/src/coreHandlers/util/addNetworkBreadcrumb.ts @@ -20,6 +20,7 @@ export function addNetworkBreadcrumb( } replay.addUpdate(() => { + // oxlint-disable-next-line typescript/no-floating-promises createPerformanceSpans(replay, [result]); // Returning true will cause `addUpdate` to not flush // We do not want network requests to cause a flush. This will prevent From c85e7d0532bf75050fad41d4d2c6ce1457116cd0 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 19 Feb 2026 11:52:38 -0500 Subject: [PATCH 4/5] chore: re-enable prefer-optional-chain --- .oxlintrc.json | 5 +++-- packages/angular/src/errorhandler.ts | 2 ++ packages/angular/src/zone.ts | 2 +- packages/astro/src/integration/snippets.ts | 2 ++ packages/browser/src/eventbuilder.ts | 1 + packages/browser/src/stack-parsers.ts | 2 +- packages/browser/src/tracing/browserTracingIntegration.ts | 3 +-- packages/core/src/client.ts | 1 + packages/core/src/integrations/mcp-server/transport.ts | 1 + packages/core/src/tracing/google-genai/index.ts | 1 + packages/core/src/tracing/openai/utils.ts | 1 + packages/core/src/tracing/vercel-ai/index.ts | 2 +- packages/core/src/utils/prepareEvent.ts | 1 + packages/nextjs/src/client/clientNormalizationIntegration.ts | 2 +- .../nextjs/src/common/devErrorSymbolicationEventProcessor.ts | 1 + .../src/integrations/local-variables/local-variables-sync.ts | 1 + .../node-core/src/integrations/local-variables/worker.ts | 1 + packages/node-core/src/utils/detection.ts | 1 + packages/react/src/hoist-non-react-statics.ts | 2 +- .../react/src/reactrouter-compat-utils/instrumentation.tsx | 4 ++-- .../react/src/reactrouter-compat-utils/route-manifest.ts | 2 +- packages/remix/src/client/performance.tsx | 2 +- .../replay-internal/src/coreHandlers/handleAfterSendEvent.ts | 2 +- packages/replay-internal/src/replay.ts | 3 +-- packages/replay-internal/src/util/handleRecordingEmit.ts | 2 +- packages/sveltekit/src/server-common/handle.ts | 1 + 26 files changed, 31 insertions(+), 17 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 118e9815210a..484b222da30b 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -67,9 +67,9 @@ "typescript/unbound-method": "error", "typescript/no-explicit-any": "error", "typescript/no-empty-function": "off", + "typescript/prefer-optional-chain": ["error"], // === FIXME: Rules to turn back as error === - "typescript/prefer-optional-chain": "warn", "typescript/no-unsafe-member-access": "warn" } }, @@ -110,7 +110,8 @@ "typescript/no-floating-promises": "off", "typescript/unbound-method": "off", "max-lines": "off", - "complexity": "off" + "complexity": "off", + "typescript/prefer-optional-chain": "off" } }, { diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index ceb05c0b9e9f..ccd918667f00 100644 --- a/packages/angular/src/errorhandler.ts +++ b/packages/angular/src/errorhandler.ts @@ -26,6 +26,7 @@ export interface ErrorHandlerOptions { function tryToUnwrapZonejsError(error: unknown): unknown | Error { // TODO: once Angular14 is the minimum requirement ERROR_ORIGINAL_ERROR and // getOriginalError from error.ts can be used directly. + // oxlint-disable-next-line typescript/prefer-optional-chain return error && (error as { ngOriginalError: Error }).ngOriginalError ? (error as { ngOriginalError: Error }).ngOriginalError : error; @@ -39,6 +40,7 @@ function extractHttpModuleError(error: HttpErrorResponse): string | Error { // ... or an`ErrorEvent`, which can provide us with the message but no stack... // guarding `ErrorEvent` against `undefined` as it's not defined in Node environments + // oxlint-disable-next-line typescript/prefer-optional-chain if (typeof ErrorEvent !== 'undefined' && error.error instanceof ErrorEvent && error.error.message) { return error.error.message; } diff --git a/packages/angular/src/zone.ts b/packages/angular/src/zone.ts index 22f56e4c3871..fb4db2ed8ac2 100644 --- a/packages/angular/src/zone.ts +++ b/packages/angular/src/zone.ts @@ -7,7 +7,7 @@ declare const Zone: any; // In Angular 17 and future versions, zoneless support is forthcoming. // Therefore, it's advisable to safely check whether the `run` function is // available in the `` context. -// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access typescript/prefer-optional-chain const isNgZoneEnabled = typeof Zone !== 'undefined' && Zone.root?.run; /** diff --git a/packages/astro/src/integration/snippets.ts b/packages/astro/src/integration/snippets.ts index 82278da28925..c97ca2fd1ed0 100644 --- a/packages/astro/src/integration/snippets.ts +++ b/packages/astro/src/integration/snippets.ts @@ -59,6 +59,7 @@ const buildClientIntegrations = (options: SentryOptions): string => { integrations.push('Sentry.browserTracingIntegration()'); } + /* oxlint-disable typescript/prefer-optional-chain */ if ( options.replaysSessionSampleRate == null || options.replaysSessionSampleRate || @@ -67,6 +68,7 @@ const buildClientIntegrations = (options: SentryOptions): string => { ) { integrations.push('Sentry.replayIntegration()'); } + /* oxlint-enable typescript/prefer-optional-chain */ return integrations.join(', '); }; diff --git a/packages/browser/src/eventbuilder.ts b/packages/browser/src/eventbuilder.ts index 9823d596a502..798a068b5adf 100644 --- a/packages/browser/src/eventbuilder.ts +++ b/packages/browser/src/eventbuilder.ts @@ -165,6 +165,7 @@ function getPopFirstTopFrames(ex: Error & { framesToPop?: unknown }): number { function isWebAssemblyException(exception: unknown): exception is WebAssembly.Exception { // Check for support // @ts-expect-error - WebAssembly.Exception is a valid class + // oxlint-disable-next-line typescript/prefer-optional-chain if (typeof WebAssembly !== 'undefined' && typeof WebAssembly.Exception !== 'undefined') { // @ts-expect-error - WebAssembly.Exception is a valid class return exception instanceof WebAssembly.Exception; diff --git a/packages/browser/src/stack-parsers.ts b/packages/browser/src/stack-parsers.ts index 02c3a1f66af3..cb74bc1e6ce6 100644 --- a/packages/browser/src/stack-parsers.ts +++ b/packages/browser/src/stack-parsers.ts @@ -88,7 +88,7 @@ const chromeStackParserFn: StackLineParserFn = line => { const parts = chromeRegex.exec(line) as null | [string, string, string, string, string]; if (parts) { - const isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line + const isEval = parts[2]?.indexOf('eval') === 0; // start of line if (isEval) { const subMatch = chromeEvalRegex.exec(parts[2]) as null | [string, string, string, string]; diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index c71acf106258..dbcc57a6d982 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -503,8 +503,7 @@ export const browserTracingIntegration = ((options: Partial { throw _makeDoNotSendEventError('An event processor returned `null`, will not send event.'); } + // oxlint-disable-next-line typescript/prefer-optional-chain const isInternalException = hint.data && (hint.data as { __sentry__: boolean }).__sentry__ === true; if (isInternalException) { return prepared; diff --git a/packages/core/src/integrations/mcp-server/transport.ts b/packages/core/src/integrations/mcp-server/transport.ts index 5e4fd6e75c23..065fb44ab17d 100644 --- a/packages/core/src/integrations/mcp-server/transport.ts +++ b/packages/core/src/integrations/mcp-server/transport.ts @@ -105,6 +105,7 @@ export function wrapTransportSend(transport: MCPTransport, options: ResolvedMcpO } if (isJsonRpcResponse(message)) { + // oxlint-disable-next-line typescript/prefer-optional-chain if (message.id !== null && message.id !== undefined) { if (message.error) { captureJsonRpcErrorResponse(message.error); diff --git a/packages/core/src/tracing/google-genai/index.ts b/packages/core/src/tracing/google-genai/index.ts index 7781b67d6db0..369fb7701bd9 100644 --- a/packages/core/src/tracing/google-genai/index.ts +++ b/packages/core/src/tracing/google-genai/index.ts @@ -143,6 +143,7 @@ function addPrivateRequestAttributes(span: Span, params: Record // config.systemInstruction: ContentUnion if ( + // oxlint-disable-next-line typescript/prefer-optional-chain 'config' in params && params.config && typeof params.config === 'object' && diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index 82494f7ae018..3338d4524d75 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -222,6 +222,7 @@ export function addResponsesApiAttributes(span: Span, response: OpenAIResponseOb // Filter for function_call type objects in the output array const functionCalls = responseWithOutput.output.filter( (item): unknown => + // oxlint-disable-next-line typescript/prefer-optional-chain typeof item === 'object' && item !== null && (item as Record).type === 'function_call', ); diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index 919c06eb12d6..f6fbee6d68f5 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -135,7 +135,7 @@ function vercelAiEventProcessor(event: Event): Event { // Also apply to root when it is the invoke_agent pipeline const trace = event.contexts?.trace; - if (trace && trace.op === 'gen_ai.invoke_agent') { + if (trace?.op === 'gen_ai.invoke_agent') { applyAccumulatedTokens(trace, tokenAccumulator); } } diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index 6528873c3dee..95e244df2092 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -95,6 +95,7 @@ export function prepareEvent( ]; // Skip event processors for internal exceptions to prevent recursion + // oxlint-disable-next-line typescript/prefer-optional-chain const isInternalException = hint.data && (hint.data as { __sentry__: boolean }).__sentry__ === true; const result = isInternalException ? resolvedSyncPromise(prepared) diff --git a/packages/nextjs/src/client/clientNormalizationIntegration.ts b/packages/nextjs/src/client/clientNormalizationIntegration.ts index c92147c82bbe..30c57ee8fc02 100644 --- a/packages/nextjs/src/client/clientNormalizationIntegration.ts +++ b/packages/nextjs/src/client/clientNormalizationIntegration.ts @@ -18,7 +18,7 @@ export const nextjsClientStackFrameNormalizationIntegration = defineIntegration( iteratee: frame => { if (experimentalThirdPartyOriginStackFrames) { // Not sure why but access to global WINDOW from @sentry/Browser causes hideous ci errors - // eslint-disable-next-line no-restricted-globals + // oxlint-disable-next-line typescript/prefer-optional-chain no-restricted-globals const windowOrigin = typeof window !== 'undefined' && window.location ? window.location.origin : ''; // A filename starting with the local origin and not ending with JS is most likely JS in HTML which we do not want to rewrite if (frame.filename?.startsWith(windowOrigin) && !frame.filename.endsWith('.js')) { diff --git a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts index 3b02d92d80fb..c238e13efdf4 100644 --- a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts +++ b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts @@ -73,6 +73,7 @@ export async function devErrorSymbolicationEventProcessor(event: Event, hint: Ev // Due to changes across Next.js versions, there are a million things that can go wrong here so we just try-catch the // entire event processor. Symbolicated stack traces are just a nice to have. try { + // oxlint-disable-next-line typescript/prefer-optional-chain if (hint.originalException && hint.originalException instanceof Error && hint.originalException.stack) { const frames = stackTraceParser.parse(hint.originalException.stack); const nextJsVersion = globalWithInjectedValues._sentryNextJsVersion; diff --git a/packages/node-core/src/integrations/local-variables/local-variables-sync.ts b/packages/node-core/src/integrations/local-variables/local-variables-sync.ts index b2af37b0c7fb..66e78dd123fe 100644 --- a/packages/node-core/src/integrations/local-variables/local-variables-sync.ts +++ b/packages/node-core/src/integrations/local-variables/local-variables-sync.ts @@ -205,6 +205,7 @@ class AsyncSession implements DebugSession { private _unrollOther(prop: Runtime.PropertyDescriptor, vars: Variables, next: (vars: Variables) => void): void { if (prop.value) { if ('value' in prop.value) { + // oxlint-disable-next-line typescript/prefer-optional-chain if (prop.value.value === undefined || prop.value.value === null) { vars[prop.name] = `<${prop.value.value}>`; } else { diff --git a/packages/node-core/src/integrations/local-variables/worker.ts b/packages/node-core/src/integrations/local-variables/worker.ts index 304c7d527ef7..c0e7b5f07bde 100644 --- a/packages/node-core/src/integrations/local-variables/worker.ts +++ b/packages/node-core/src/integrations/local-variables/worker.ts @@ -46,6 +46,7 @@ function unrollOther(prop: Runtime.PropertyDescriptor, vars: Variables): void { } if ('value' in prop.value) { + // oxlint-disable-next-line typescript/prefer-optional-chain if (prop.value.value === undefined || prop.value.value === null) { vars[prop.name] = `<${prop.value.value}>`; } else { diff --git a/packages/node-core/src/utils/detection.ts b/packages/node-core/src/utils/detection.ts index f7ae9a792c27..435f3b2a6686 100644 --- a/packages/node-core/src/utils/detection.ts +++ b/packages/node-core/src/utils/detection.ts @@ -4,6 +4,7 @@ import { NODE_MAJOR, NODE_MINOR } from '../nodeVersion'; /** Detect CommonJS. */ export function isCjs(): boolean { try { + // oxlint-disable-next-line typescript/prefer-optional-chain return typeof module !== 'undefined' && typeof module.exports !== 'undefined'; } catch { return false; diff --git a/packages/react/src/hoist-non-react-statics.ts b/packages/react/src/hoist-non-react-statics.ts index 792e05584e38..2e150c4350d1 100644 --- a/packages/react/src/hoist-non-react-statics.ts +++ b/packages/react/src/hoist-non-react-statics.ts @@ -146,7 +146,7 @@ export function hoistNonReactStatics< // Use key directly - String(key) throws for Symbols if minified to '' + key (#18966) if ( !KNOWN_STATICS[key as keyof typeof KNOWN_STATICS] && - !(excludelist && excludelist[key as keyof C]) && + !excludelist?.[key as keyof C] && !sourceStatics?.[key as string] && !targetStatics?.[key as string] && !getOwnPropertyDescriptor(targetComponent, key) // Don't overwrite existing properties diff --git a/packages/react/src/reactrouter-compat-utils/instrumentation.tsx b/packages/react/src/reactrouter-compat-utils/instrumentation.tsx index e73438312676..54c344956948 100644 --- a/packages/react/src/reactrouter-compat-utils/instrumentation.tsx +++ b/packages/react/src/reactrouter-compat-utils/instrumentation.tsx @@ -614,8 +614,8 @@ export function createV6CompatibleWrapCreateMemoryRouter< const initialEntries = opts?.initialEntries; const initialIndex = opts?.initialIndex; - const hasOnlyOneInitialEntry = initialEntries && initialEntries.length === 1; - const hasIndexedEntry = initialIndex !== undefined && initialEntries && initialEntries[initialIndex]; + const hasOnlyOneInitialEntry = initialEntries?.length === 1; + const hasIndexedEntry = initialIndex !== undefined && initialEntries?.[initialIndex]; initialEntry = hasOnlyOneInitialEntry ? initialEntries[0] diff --git a/packages/react/src/reactrouter-compat-utils/route-manifest.ts b/packages/react/src/reactrouter-compat-utils/route-manifest.ts index 6160cad657c3..bdc49f76705e 100644 --- a/packages/react/src/reactrouter-compat-utils/route-manifest.ts +++ b/packages/react/src/reactrouter-compat-utils/route-manifest.ts @@ -36,7 +36,7 @@ const SORTED_MANIFEST_CACHE = new WeakMap(); * Optionally strips a basename prefix before matching. */ export function matchRouteManifest(pathname: string, manifest: string[], basename?: string): string | null { - if (!pathname || !manifest || !manifest.length) { + if (!pathname || !manifest?.length) { return null; } diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index 213f4eb43176..b3cde64d72de 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -169,7 +169,7 @@ export function withSentry

, R extends React.Co const matches = _useMatches(); _useEffect(() => { - const lastMatch = matches && matches[matches.length - 1]; + const lastMatch = matches?.[matches.length - 1]; if (lastMatch) { const { name, source } = getTransactionNameAndSource(location.pathname, lastMatch.id); diff --git a/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts b/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts index 4df1b62532ac..ae0f6ad86be4 100644 --- a/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts +++ b/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts @@ -57,7 +57,7 @@ function handleErrorEvent(replay: ReplayContainer, event: ErrorEvent): void { // If error event is tagged with replay id it means it was sampled (when in buffer mode) // Need to be very careful that this does not cause an infinite loop - if (replay.recordingMode !== 'buffer' || !event.tags || !event.tags.replayId) { + if (replay.recordingMode !== 'buffer' || !event.tags?.replayId) { return; } diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index 10dba8758d8a..cab408ca9d5d 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -748,8 +748,7 @@ export class ReplayContainer implements ReplayContainerInterface { if ( this._lastActivity && isExpired(this._lastActivity, this.timeouts.sessionIdlePause) && - this.session && - this.session.sampled === 'session' + this.session?.sampled === 'session' ) { // Pause recording only for session-based replays. Otherwise, resuming // will create a new replay and will conflict with users who only choose diff --git a/packages/replay-internal/src/util/handleRecordingEmit.ts b/packages/replay-internal/src/util/handleRecordingEmit.ts index aeb49f0cd259..215f94daa1db 100644 --- a/packages/replay-internal/src/util/handleRecordingEmit.ts +++ b/packages/replay-internal/src/util/handleRecordingEmit.ts @@ -146,7 +146,7 @@ export function createOptionsEvent(replay: ReplayContainer): ReplayOptionFrameEv */ function addSettingsEvent(replay: ReplayContainer, isCheckout?: boolean): void { // Only need to add this event when sending the first segment - if (!isCheckout || !replay.session || replay.session.segmentId !== 0) { + if (!isCheckout || replay.session?.segmentId !== 0) { return; } diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index 26872a0f6f24..761445739305 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -254,6 +254,7 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { // Escape hatch to suppress request isolation and trace continuation (see initCloudflareSentryHandle) const skipIsolation = + // oxlint-disable-next-line typescript/prefer-optional-chain '_sentrySkipRequestIsolation' in backwardsForwardsCompatibleEvent.locals && backwardsForwardsCompatibleEvent.locals._sentrySkipRequestIsolation; From e9ad91b81d0db2101c3f1fb8286d904eb56700ab Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 19 Feb 2026 11:57:28 -0500 Subject: [PATCH 5/5] chore: enable no-unsafe-member-access --- .oxlintrc.json | 7 ++----- packages/astro/src/server/middleware.ts | 3 ++- packages/nuxt/src/runtime/utils.ts | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 484b222da30b..1d05d5b782dc 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -63,14 +63,11 @@ "typescript/prefer-for-of": "error", "typescript/no-floating-promises": ["error", { "ignoreVoid": true }], "typescript/no-dynamic-delete": "error", - // "typescript/no-unsafe-member-access": "error", + "typescript/no-unsafe-member-access": "error", "typescript/unbound-method": "error", "typescript/no-explicit-any": "error", "typescript/no-empty-function": "off", - "typescript/prefer-optional-chain": ["error"], - - // === FIXME: Rules to turn back as error === - "typescript/no-unsafe-member-access": "warn" + "typescript/prefer-optional-chain": ["error"] } }, { diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index a12c25ff6045..b42be72ae846 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -423,9 +423,10 @@ function getParametrizedRoute( // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const routesFromManifest = ctx?.[Symbol.for('context.routes')]?.manifest?.routes; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + // oxlint-disable-next-line typescript/no-unsafe-member-access const matchedRouteSegmentsFromManifest = routesFromManifest?.find( (route: { routeData?: { route?: string } }) => route?.routeData?.route === rawRoutePattern, + // oxlint-disable-next-line typescript/no-unsafe-member-access )?.routeData?.segments; return ( diff --git a/packages/nuxt/src/runtime/utils.ts b/packages/nuxt/src/runtime/utils.ts index ce8069c58cdb..2c3526b951c9 100644 --- a/packages/nuxt/src/runtime/utils.ts +++ b/packages/nuxt/src/runtime/utils.ts @@ -72,6 +72,7 @@ export function reportNuxtError(options: { const sentryOptions = sentryClient ? (sentryClient.getOptions() as ClientOptions & VueOptions) : null; // `attachProps` is enabled by default and props should only not be attached if explicitly disabled (see DEFAULT_CONFIG in `vueIntegration`). + // oxlint-disable-next-line typescript/no-unsafe-member-access if (sentryOptions?.attachProps && instance.$props !== false) { metadata.propsData = instance.$props; }