Skip to content

build(oxlint): upgrade oxlint to 1.75 and tsgolint to TS 7 - #22561

Merged
logaretm merged 7 commits into
developfrom
awad/upgrade-oxlint
Jul 28, 2026
Merged

build(oxlint): upgrade oxlint to 1.75 and tsgolint to TS 7#22561
logaretm merged 7 commits into
developfrom
awad/upgrade-oxlint

Conversation

@logaretm

@logaretm logaretm commented Jul 23, 2026

Copy link
Copy Markdown
Member

Upgrades oxlint (1.53 to 1.75) and oxlint-tsgolint (0.16 to 7, now on TS 7), and removes the OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS workaround from every lint script so real type-aware diagnostics run again.

  • Rules dropped: Those were removed upstream (quotes is oxfmt's job now,import/no-unresolved, import/no-extraneous-dependencies, jsdoc/require-jsdoc, react/prop-types).
  • Rules renamed or moved to a different group: no-return-await becomes typescript/return-await.

Also ignores some new rules like unicorn that introduced a bunch of warnings, I see some useful stuff in there so I may re-enable them one by one later.

One of the changes surfaced a very flakey test that relies on a microtick timing, i padded it to make it more reliable.

@logaretm logaretm changed the title awad/upgrade oxlint build(deps): upgrade oxlint to 1.75, tsgolint to TS 7, oxfmt to 0.60 Jul 23, 2026
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 29.84 kB -0.04% -9 B 🔽
@sentry/browser - with treeshaking flags 28.05 kB -0.03% -8 B 🔽
@sentry/browser (incl. Tracing) 47.41 kB -0.03% -11 B 🔽
@sentry/browser (incl. Tracing + Span Streaming) 47.42 kB -0.03% -10 B 🔽
@sentry/browser (incl. Tracing, Profiling) 52.15 kB -0.03% -11 B 🔽
@sentry/browser (incl. Tracing, Replay) 86.74 kB -0.01% -6 B 🔽
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 76.15 kB -0.02% -9 B 🔽
@sentry/browser (incl. Tracing, Replay with Canvas) 91.47 kB -0.01% -8 B 🔽
@sentry/browser (incl. Tracing, Replay, Feedback) 104.11 kB -0.01% -9 B 🔽
@sentry/browser (incl. Feedback) 47.15 kB -0.02% -8 B 🔽
@sentry/browser (incl. sendFeedback) 34.68 kB -0.03% -9 B 🔽
@sentry/browser (incl. FeedbackAsync) 39.77 kB -0.03% -8 B 🔽
@sentry/browser (incl. Metrics) 30.91 kB -0.03% -7 B 🔽
@sentry/browser (incl. Logs) 31.14 kB -0.02% -6 B 🔽
@sentry/browser (incl. Metrics & Logs) 31.82 kB -0.02% -6 B 🔽
@sentry/react 31.63 kB -0.03% -7 B 🔽
@sentry/react (incl. Tracing) 49.63 kB -0.02% -5 B 🔽
@sentry/vue 34.76 kB -0.03% -8 B 🔽
@sentry/vue (incl. Tracing) 49.36 kB -0.02% -9 B 🔽
@sentry/svelte 29.87 kB -0.04% -9 B 🔽
CDN Bundle 31.89 kB -0.02% -5 B 🔽
CDN Bundle (incl. Tracing) 47.76 kB -0.01% -2 B 🔽
CDN Bundle (incl. Logs, Metrics) 33.43 kB -0.02% -4 B 🔽
CDN Bundle (incl. Tracing, Logs, Metrics) 49.14 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 72.79 kB -0.01% -7 B 🔽
CDN Bundle (incl. Tracing, Replay) 85.39 kB -0.01% -6 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 86.67 kB -0.01% -4 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) 91.16 kB -0.01% -8 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 92.46 kB -0.01% -8 B 🔽
CDN Bundle - uncompressed 95.09 kB -0.04% -38 B 🔽
CDN Bundle (incl. Tracing) - uncompressed 143.22 kB -0.03% -41 B 🔽
CDN Bundle (incl. Logs, Metrics) - uncompressed 99.8 kB -0.04% -38 B 🔽
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 147.2 kB -0.03% -41 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 224.56 kB -0.02% -41 B 🔽
CDN Bundle (incl. Tracing, Replay) - uncompressed 262.48 kB -0.02% -41 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 266.45 kB -0.02% -41 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 276.19 kB -0.02% -41 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 280.14 kB -0.02% -41 B 🔽
@sentry/nextjs (client) 52.23 kB -0.01% -5 B 🔽
@sentry/sveltekit (client) 47.83 kB -0.03% -10 B 🔽
@sentry/core/server 79.72 kB -0.01% -3 B 🔽
@sentry/core/browser 51.6 kB -0.02% -7 B 🔽
@sentry/node 122.35 kB - -
@sentry/node/import (ESM hook with diagnostics-channel injection) 166 B - -
@sentry/node - without tracing 85.75 kB +0.01% +2 B 🔺
@sentry/aws-serverless 93.78 kB +0.01% +1 B 🔺
@sentry/cloudflare (withSentry) - minified 197.21 kB -0.03% -49 B 🔽
@sentry/cloudflare (withSentry) 484.94 kB -0.07% -300 B 🔽

View base workflow run

@logaretm
logaretm force-pushed the awad/upgrade-oxlint branch from 02b93b1 to af518ac Compare July 23, 2026 16:20
@logaretm logaretm changed the title build(deps): upgrade oxlint to 1.75, tsgolint to TS 7, oxfmt to 0.60 build(oxlint): upgrade oxlint to 1.75 and tsgolint to TS 7 Jul 23, 2026
@logaretm
logaretm force-pushed the awad/upgrade-oxlint branch from af518ac to 977d8d4 Compare July 23, 2026 16:49
Base automatically changed from awad/ts7-tsconfig-fixes to develop July 23, 2026 17:06
@logaretm
logaretm force-pushed the awad/upgrade-oxlint branch from 977d8d4 to d5565e9 Compare July 23, 2026 17:06
// If we have no comparison branch, we just run size limit & store the result as artifact
if (!comparisonBranch) {
return runSizeLimitOnComparisonBranch();
return await runSizeLimitOnComparisonBranch();

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.

Example of the new return await rule detecting returning promises inside try/catch blocks.

@logaretm
logaretm force-pushed the awad/upgrade-oxlint branch 4 times, most recently from d38291a to 3f6b2e1 Compare July 27, 2026 15:50
@logaretm
logaretm marked this pull request as ready for review July 27, 2026 16:57
@logaretm
logaretm requested review from a team as code owners July 27, 2026 16:57
@logaretm
logaretm requested review from JPeer264, Lms24, isaacs, msonnb, nicohrubec and s1gr1d and removed request for a team July 27, 2026 16:57
@logaretm
logaretm force-pushed the awad/upgrade-oxlint branch 2 times, most recently from e4e78c5 to 5efdd5d Compare July 27, 2026 19:58

@s1gr1d s1gr1d left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see a lot of removed type casts - is this because the type can be better inferred now or the linting does not allow the casts anymore?
Would be good to still be able to cast when necessary, also to better see which type we would expect, when it's not inferred (however, we can always opt-out with a disable comment)

Comment on lines +77 to +84
const exceptionValue: Exception = {
type: isEvent(exception) ? exception.constructor.name : isUnhandledRejection ? 'UnhandledRejection' : 'Error',
value: getNonErrorObjectExceptionValue(exception, { isUnhandledRejection }),
};

const event = {
exception: {
values: [
{
type: isEvent(exception) ? exception.constructor.name : isUnhandledRejection ? 'UnhandledRejection' : 'Error',
value: getNonErrorObjectExceptionValue(exception, { isUnhandledRejection }),
} as Exception,
],
values: [exceptionValue],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Q: which rule is that? Or was this just a small refactor?

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.

no-unnecessary-type-assertion, I think it was always there but never worked due to the diagnostic suppression we had in place to get it to work with TS 5

@logaretm

logaretm commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

@s1gr1d no-unnecessary-type-assertion is the rule that triggered the most here, it was always enabled but never worked due to us suppressing the TS 7.0 errors to get it semi working with TS 5.0.

Basically it is able to infer types better now and is able to determine if a type is unnecessary. Rule def can be found here.

Would be good to still be able to cast when necessary

Yep we still are able to, it doesn't ban casts, it bans the ones it deems unnecessary. In all of these cases we already had the right types inferred. I checked most of them. I think it's probably due to our codebase drifting around or us tightening the types in unrelated work.

Note there are indeed a few false positives but around 7% of the cases here, so I think its worthwhile.

logaretm added 7 commits July 28, 2026 09:42
Renames/drops the rules the new versions flag as unknown: `no-return-await`
becomes `typescript/return-await` (kept on the modern in-try-catch default),
and rules removed upstream are dropped since they no longer exist natively:
`quotes` (now owned by oxfmt), `import/no-unresolved` (was off everywhere),
`import/no-extraneous-dependencies`, `jsdoc/require-jsdoc`, and
`react/prop-types`.

Also removes the `OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS`
workaround from every lint script so tsgolint surfaces real program
diagnostics again.
Clears the type-aware errors that appeared once real program diagnostics
were re-enabled:

- Removes unnecessary type assertions and the now-unused type imports they
  leave behind (oxlint --fix), and drops a dead pino integration interface.
- Consolidates the dedupe stacktrace/fingerprint guards so narrowing works
  without the old assignment-cast idiom.
- Migrates the terser plugin from the deprecated `output` option to `format`.
- Renames stale disable directives to the new rule names
  (`typescript-eslint(...)` -> `typescript/...`) for unbound-method and the
  HTTP_URL no-deprecated suppressions.
- Adds no-deprecated suppressions where we intentionally read our own
  deprecated options for back-compat, and return-await suppressions where an
  await is deliberate (node-cron) or the cast makes the rule misfire.
…ssary

`no-unnecessary-type-assertion` (tsgolint) reports several casts as redundant,
but they are load-bearing: it only checks assignability at the cast site and
misses that the cast changes the expression's resulting type for downstream
use. Removing them type-checks locally but fails the real `tsc` build (verified:
both tsconfig.json and tsconfig.types.json error identically without the cast).

Restores each cast with a targeted suppression: a Cloudflare-only client option,
LCP/INP PerformanceEntry fields, a Vue VNode probe, mysql/postgres vendored
connection shapes, the view-hierarchy identifier, and the internal
`shouldNotThrowOnFailure` on setCommits. eventbuilder instead uses a typed
`Exception` variable so no cast (or suppression) is needed there.
The oxlint 1.75 bump surfaced ~5.9k warnings. This clears them to zero without
churning the codebase:

- Vitest: keep the plugin (and its ~60 other rules) but disable the correctness
  rules the bump newly fired across the existing suite (no-standalone-expect,
  require-mock-type-parameters, no-conditional-expect, expect-expect, valid-title,
  valid-expect, require-to-throw-message, no-disabled-tests, valid-describe-callback).
- Disable rules that over-trigger / false-positive here: no-useless-default-assignment,
  no-useless-fallback-in-spread, no-thenable (we implement thenables), no-new-array
  (used with immediate .fill()), prefer-string-starts-ends-with.
- Keep no-empty-file on (catches accidental empties) but exempt the two intentional
  comment-only files, and silence test-file occurrences of the general rules.
- Fix the few worth fixing: drop redundant `void` operators in the graphql/firebase
  channel callbacks, type the bun test-runner fetch init as RequestInit, and suppress
  the two no-unreachable false positives in rollup dual-build helpers.

Note: vitest/valid-expect flagged 10 pre-existing broken tests (chai-style `.to.`
modifiers); left for a separate fix.
…base

The develop rebase reintroduced the OTel span casts (its span-kind refactor
touched this file); tsgolint flags them as unnecessary again, so drop them
while keeping develop's `attributes ... || {}` change.
waitForReplayEventBuffer hardcoded "one Promise.resolve() per await in the util
functions", so it silently coupled the test to the enrichment chain's exact
await count. That made a behavior-neutral lint fix (dropping a redundant
`return await` in _getResponseText) look like a data-loss regression.

Flush microtasks in a bounded loop instead, so the test no longer depends on
the implementation's await depth. _getResponseText keeps the rule-compliant
`return response.text()`.
Astro's middleware no longer needs the `next` cast (unnecessary post-rebase),
and the new Durable Object storage allowlist cast gets the same targeted
`no-unnecessary-type-assertion` suppression as its SQL sibling, since the
Cloudflare-only option is invisible to the rule but required by tsc.
@logaretm
logaretm force-pushed the awad/upgrade-oxlint branch from 5efdd5d to e0447c5 Compare July 28, 2026 13:57
@isaacs

isaacs commented Jul 28, 2026

Copy link
Copy Markdown
Member

Yep we still are able to, it doesn't ban casts, it bans the ones it deems unnecessary.

I've noticed that LLMs are often very liberal in putting typecasts everywhere. It is often the safer option and simplest way to avoid a type conflict, which I guess is why they do it, but it has led to a lot of unnecessary casts (which then sometimes Bugbot complains about in the PR), so having a linter that removes these is nice. 👍

@logaretm
logaretm merged commit 5600cd5 into develop Jul 28, 2026
268 of 270 checks passed
@logaretm
logaretm deleted the awad/upgrade-oxlint branch July 28, 2026 14:47
},
},
output: {
format: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: why did this change?

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.

deprecated API, was being flagged by the linter which I assume due to a direct result to the type awareness being "fully" aware now.

},
"scripts": {
"lint:fix": "OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS=true oxlint . --fix --type-aware",
"lint": "OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS=true oxlint . --type-aware",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So nice to drop this giant env flag 🏁

// _safeRead falls back to direct property access.
}
// oxlint-enable typescript-eslint(unbound-method)
// oxlint-enable typescript/unbound-method

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, oxlint finally aligning with every other linter's way of identifying rules. <3 <3 <3

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.

this was so annoying, yes!

{ name: 'glob', scope: sentryScope },
async () => await globFiles(globAssets, { ignore: options.sourcemaps?.ignore }),
const globResult = await startSpan({ name: 'glob', scope: sentryScope }, async () =>
globFiles(globAssets, { ignore: options.sourcemaps?.ignore }),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This return-await rule is quite nice. I like it.

Comment on lines +116 to 117
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- rule false positive: the cast carries the internal `shouldNotThrowOnFailure` field; tsc errors without it
setCommits: userOptions.release?.setCommits as

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm pretty sure you could do this without the override by doing something like this up above:

const setCommits:
  | (SetCommitsOptions & { shouldNotThrowOnFailure?: boolean })
  | false
  | undefined = userOptions.release?.setCommits;

and then replacing this line with:

      setCommits,

But, the override is fine, too. Maybe better, since otherwise it'd move the casting assignment way out of view.

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.

it looks like it doesn't take the type narrowing into account, it checks if the operand is assignable already to the cast and if it is then it flags it.

There are a few filed bugs with it but I think if the type cast is a superset of the inferred type, it should not flag it. Well, ideally that is.

Comment on lines +62 to 63
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- rule false positive: the cast reaches the Cloudflare-only `durableObjectStorageSpanAllowlist`; tsc errors without it
const allowlist = (getClient()?.getOptions() as CloudflareClientOptions | undefined)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would this work?

Suggested change
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- rule false positive: the cast reaches the Cloudflare-only `durableObjectStorageSpanAllowlist`; tsc errors without it
const allowlist = (getClient()?.getOptions() as CloudflareClientOptions | undefined)
const allowlist: CloudflareClientOptions | undefined = getClient()?.getOptions()

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.

Could work if I extract the options to its own variable up there. I ignored for now since I think this is a false positive.

Comment on lines +33 to 35
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- rule false positive: the cast reaches the Cloudflare-only `durableObjectSqlSpanAllowlist`; tsc errors without it
const allowlist = (getClient()?.getOptions() as CloudflareClientOptions | undefined)
?.durableObjectSqlSpanAllowlist;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here, maybe?

Suggested change
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- rule false positive: the cast reaches the Cloudflare-only `durableObjectSqlSpanAllowlist`; tsc errors without it
const allowlist = (getClient()?.getOptions() as CloudflareClientOptions | undefined)
?.durableObjectSqlSpanAllowlist;
const allowlist: CloudflareClientOptions | undefined = getClient()?.getOptions()?.durableObjectSqlSpanAllowlist;

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.

Yea probably, I merged before seeing your comments. I see this as a false positive so I just ignored the rule there.

let currentFrames = getFramesFromEvent(currentEvent);
let previousFrames = getFramesFromEvent(previousEvent);
const currentFrames = getFramesFromEvent(currentEvent);
const previousFrames = getFramesFromEvent(previousEvent);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants