Skip to content

chore(devops): move lint coverage to oxlint - #6076

Merged
Celant merged 3 commits into
mainfrom
chore/oxlint-take-over-eslint
Oct 23, 2026
Merged

Celant merged 3 commits into
mainfrom
chore/oxlint-take-over-eslint

Conversation

@VariableVince

@VariableVince VariableVince commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Resolves #6071
Refs #6025

Important

Stacked PR 2 of 3. They need to be merged in order (1 first), not per se at the same time but is preferred.

Follow-up to #3866 and the OXC toolchain upgrade in #6074. This moves the lint coverage we want to retain from ESLint to Oxlint. ESLint remains in place for the next PR, which removes its remaining dependencies and configuration.

Oxlint configuration

  • Enable the correctness category and the import plugin's no-duplicates rule.
  • Keep the existing ESLint rule behavior: typescript/no-explicit-any off, with typescript/prefer-nullish-coalescing, eqeqeq, no-case-declarations, and the existing unused-variable settings enabled.
  • Keep these type-aware rules explicitly off. ESLint did not enforce them, and they are not part of this migration:
    • typescript/no-floating-promises
    • typescript/restrict-template-expressions
    • typescript/unbound-method
    • typescript/no-base-to-string
    • typescript/no-misused-spread
    • typescript/require-array-sort-compare
    • typescript/no-redundant-type-constituents
    • typescript/await-thenable
    • typescript/no-useless-default-assignment
  • Add .claude/** to Oxlint's ignore patterns to match ESLint's existing ignore.
  • Add scoped engine-package overrides to preserve the deterministic-simulation restrictions.

eslint/no-restricted-properties is native to Oxlint and preserves the Math.* restrictions. The non-native no-restricted-syntax rule runs through the official oxlint-plugin-eslint package added in #6074. Both guards retain their existing paths and behavior.

Import deduplication (#6025)

Follow-up to #6026. In this PR because it is part of oxlint. Enable import/no-duplicates to restore import deduplication previously supplied by prettier-plugin-organize-imports. Oxlint's rule has a working auto-fix and correctly handles type imports. Also considered eslint/no-duplicate-imports, but that one has no auto-fix here and reports intentional type/value import pairs as duplicates.

Safe fixes merge duplicate imports. A small number of side-effect imports must remain separate from type-only imports: TypeScript erases the named type import at runtime, while the side-effect import registers a custom element. Commented those cases with Oxlint suppressions with explanations, so these imports aren't removed.

The one remaining prettier-plugin-organize-imports behavior gap is sorting named specifiers inside one import declaration (for example, import { zeta, alpha }). Neither Oxfmt's sortImports nor Oxlint currently does this. Existing imports are already sorted from the TypeScript organize-imports era, but they will not be automatically re-sorted going forward.

Findings fixed

Enabling correctness surfaced six eslint/no-unsafe-optional-chaining cases. These are genuine runtime risks that ESLint 10 missed because TypeScript as assertions interrupt the AST pattern its rule checks. They are fixed rather than suppressed.

Verification

  • oxlint and eslint — clean
  • oxfmt --check — clean
  • npm run typecheck — passes
  • Focused affected/API tests — 382 passed
  • Full npm test — 9,416 passed, 1 skipped. Five assertions expect en-US number grouping, while this environment defaults to nl-NL (e.g. 1.150 vs 1,150).

Local lint timing with this configuration: ESLint 45.3s, Oxlint 4.4s (about 10× faster).

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory

Please put your Discord username so you can be contacted if a bug or regression is found:

tryout33

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3dba4730-c18b-4cc0-bd4d-07b1f0acfbb1
📥 Commits

Reviewing files that changed from the base of the PR and between 59ab090 and 37c89f8.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d0e2a8b1-b2c5-4321-9dc2-9fd4e515c23d
📥 Commits

Reviewing files that changed from the base of the PR and between 7e25fc8 and 59ab090.

📒 Files selected for processing: 1
  • src/client/SinglePlayerModal.ts
💤 Files with no reviewable changes: 1
  • src/client/SinglePlayerModal.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

Walkthrough

The PR adds Oxlint plugins and rules, including selected restrictions on Math operations in engine TypeScript files. It also adjusts client and test imports, and replaces optional chaining with non-null assertions in selected test code.

Changes

Oxlint configuration and codebase updates

Layer / File(s) Summary
Configure Oxlint checks
.oxlintrc.json
Adds ignored paths and plugins, enables correctness checks and import/no-duplicates, configures TypeScript rules, and reports selected Math calls and exponentiation patterns in engine TypeScript files.
Adjust client imports
src/client/HostLobbyModal.ts, src/client/InGameModal.ts, src/client/LangSelector.ts, src/client/Main.ts, src/client/Store.ts, src/client/UserSettingModal.ts, src/client/components/*, src/client/replay/ReplayViewer.ts, src/client/SinglePlayerModal.ts
Removes standalone side-effect imports and adds comments or lint suppressions for imports retained for custom-element registration.
Update test imports and assertions
tests/ColumnPicker.test.ts, tests/DesktopStatusBar.test.ts, tests/ReachabilityGating.test.ts, tests/client/*
Removes standalone side-effect imports, documents registration imports, and uses non-null assertions in selected test queries and helpers.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Other

Possibly related issues

Suggested reviewers: evanpelle

Merge Risk: ⚪ Minimal · up to 59ab0

The selected import change preserves saved-games panel registration and is ready to merge after normal checks.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Linked Issues check Warning Issue #6071 requires removing typescript-eslint and fully replacing ESLint with Oxlint. At the reviewed head, package.json still lists eslint and typescript-eslint, retains ESLint scripts, and… Remove the remaining ESLint and typescript-eslint dependencies, configuration, scripts, and lint-staged usage. Make Oxlint the complete lint implementation required by issue #6071.
Docstring Coverage Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 45 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title "chore(devops): move lint coverage to oxlint" matches the main change. The PR moves retained ESLint rule coverage to Oxlint, enables the correctness category and import/no-duplicates, and ad…
Description check Passed The description states the PR moves retained lint coverage from ESLint to Oxlint. It lists the Oxlint configuration changes, the import deduplication work, the six optional-chaining fixes, and the ver…
Out of Scope Changes check Passed The Oxlint configuration, import deduplication changes, documented suppressions, and optional-chaining fixes all support the lint migration described for issue #6071. The reviewed summary shows no dem…
Full details: Title check

Explanation

The title "chore(devops): move lint coverage to oxlint" matches the main change. The PR moves retained ESLint rule coverage to Oxlint, enables the correctness category and import/no-duplicates, and adds ignore patterns and engine overrides. The title is short, specific, and uses a conventional commit prefix.

Full details: Description check

Explanation

The description states the PR moves retained lint coverage from ESLint to Oxlint. It lists the Oxlint configuration changes, the import deduplication work, the six optional-chaining fixes, and the verification results. It is directly related to the changeset.

Full details: Out of Scope Changes check

Explanation

The Oxlint configuration, import deduplication changes, documented suppressions, and optional-chaining fixes all support the lint migration described for issue #6071. The reviewed summary shows no demonstrated unrelated changes.

Full details: Linked Issues check

Explanation

Issue #6071 requires removing typescript-eslint and fully replacing ESLint with Oxlint. At the reviewed head, package.json still lists eslint and typescript-eslint, retains ESLint scripts, and runs ESLint from the lint and lint-staged workflows. The PR description also says ESLint remains for a later PR. The Oxlint migration in this PR does not complete the linked issue.

Resolution

Remove the remaining ESLint and typescript-eslint dependencies, configuration, scripts, and lint-staged usage. Make Oxlint the complete lint implementation required by issue #6071.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

New rules arrive to check the code
Imports take a clearer road
Custom tags still register
Tests assert what must be there
Oxlint joins the linting flow

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Verdict: No blocking issues. Findings: 0 high, 0 medium, 0 low.

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

What I checked:

  • Removed side-effect imports (Main.ts GameModeSelector/UsernameInput, Store.ts, NavAccountMenu.ts, ProfileCard.ts, ReplayViewer.ts, and the test files): each module is still imported for a runtime value (joinIsGateable, alignPurchaseRows, levelBadgeLabel, levelBadgeAccent, ShowSettingsModalEvent, new NewsModal() and others). The imports are not elided, so custom-element registration still happens.
  • Kept duplicate imports (InGameModal, LangSelector, HostLobbyModal, UserSettingModal, ListLobbyDialog test): the class is only used as a type in these files, so keeping the side-effect import with a scoped suppression is correct.
  • new Headers(...) in ClanApi/FriendsApi/SavedGamesApi: behaviour is the same for plain-object headers. It also now handles Headers instances, which object spread would have dropped. Authorization still overrides caller-supplied headers.
  • Type narrowings (Team | "kicked", PlayerID | typeof AllPlayers): Team and PlayerID are both string, so these are type-level only and the "kicked" comparisons still compile.
  • Determinism guards: the .oxlintrc.json override has the same Math.* property list and the same ** selector and paths as the ESLint config.

@VariableVince
VariableVince added this pull request to stack #6078 October 22, 2026 16:23
coderabbitai[bot]
coderabbitai Bot previously approved these changes Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Reviewed range: 836ec01..cd5e51f (incremental).

Verdict: No issues found. Checked for bugs and CLAUDE.md compliance. (0 critical / 0 major / 0 minor)

This range changes two things:

  • .oxlintrc.json switches off five more type-aware rules: no-misused-spread, require-array-sort-compare, no-redundant-type-constituents, await-thenable and no-useless-default-assignment. This matches the updated PR description.
  • It undoes the source and test edits that those rules had caused, across the client, server, engine, tests and scripts. Every changed source line now matches main exactly. Examples: TeamAssignment.ts returns Team | "kicked" again, the client API helpers spread headers again, and the tests use .sort() with no comparator again.

Since the result is identical to main, this range adds no new behavior. Any concerns about that code (such as await void copyToClipboard(...) in the game-history views) existed before this PR and are outside its scope.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Oct 22, 2026
@VariableVince

Copy link
Copy Markdown
Contributor Author

/ready

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Verdict: No issues found. Checked for bugs and CLAUDE.md compliance. (0 critical / 0 major / 0 minor)

Full re-review of the PR at 7e25fc8. Earlier Claude reviews already covered most of this, and nothing new came up:

  • .oxlintrc.json: the determinism override has the same Math.* property list, ** selector and engine package paths as before. DetMath.ts only names the restricted functions in comments, so excluding it has no effect on results.
  • Removed side-effect imports (Main.ts GameModeSelector, Store.ts, NavAccountMenu.ts, ProfileCard.ts, ReplayViewer.ts, DesktopStatusBar/ChatModalShortcut/EmojiTable/SpawnTimer/NewsModal/ReachabilityGating tests): each module is still imported for a runtime value, so the import is not elided and the custom element still registers.
  • Kept duplicate imports with suppressions (InGameModal, LangSelector, HostLobbyModal, UserSettingModal, Main.ts LangSelector/UsernameInput, ListLobbyDialog test): the named import is only used as a type in each file, so the bare side-effect import is needed.
  • ?. → ! in tests: these are the no-unsafe-optional-chaining fixes. If the value were missing, the test would fail either way, now with a clearer error.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Reviewed range: 7e25fc8 → 59ab090 (incremental re-review).

Verdict: No issues found. Checked for bugs and CLAUDE.md compliance. (0 critical / 0 major / 0 minor)

Most of this range is a merge of the base branch: the nation AI split, server join/roster changes, tests and Oxfmt reformatting of SavedGamesPanel.ts. Those hunks are not part of this PR's diff and were not reviewed.

The PR's own change in this range:

  • src/client/SinglePlayerModal.ts:25 drops the bare side-effect import "./components/SavedGamesPanel";. This is safe. The remaining import from the same module, { type LoadSavedGameDetail, savedGameIdFrom }, includes savedGameIdFrom, a runtime value used in onOpen. TypeScript therefore keeps the module import, so <saved-games-panel> stays registered. Unlike the type-only cases elsewhere in this PR, it needs no oxlint-disable comment.

Base automatically changed from eslint-oxlint-followup to main October 23, 2026 10:12
VariableVince and others added 3 commits October 23, 2026 11:12
Enable the correctness category and import/no-duplicates while
keeping the four type-aware rules that ESLint never enforced off.
Move the engine determinism restrictions to oxlint and its official
ESLint-rules JS plugin, then fix the resulting lint findings.

This includes six unsafe optional-chain cases that oxlint catches
but ESLint misses, plus the import-deduplication gap from #6025.
ESLint remains as a safety net for the removal follow-up.
Keep no-misused-spread, require-array-sort-compare,
no-redundant-type-constituents, await-thenable, and
no-useless-default-assignment explicitly disabled. These checks
were not enabled by ESLint and are outside this migration.
Remove their associated code and test changes, retaining the
six unsafe optional-chain fixes and import deduplication.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Celant added a commit that referenced this pull request Oct 23, 2026
Resolves #6073

> [!IMPORTANT]
> Stacked PR 1 of 3. They need to be merged in order (1 first), not per
se at the same time but is preferred.

Part of the TypeScript 7 preparation tracked in #4982: update the OXC
tooling before moving ESLint's remaining lint coverage to Oxlint and
removing the TypeScript-ESLint dependency.

| Package | Before | After |
| --- | --- | --- |
| `oxlint` | 1.75.0 | **1.88.0** |
| `oxlint-tsgolint` | 7.0.2001 | **7.0.2004** (required by oxlint
1.88.0: `>=7.0.2003`) |
| `oxfmt` | 0.48.0 | **0.73.0** |
| `oxlint-plugin-eslint` | — | **1.88.0** (new) |

`oxlint` 1.89.0 and `oxfmt` 0.74.0 were published on 2026-10-19, three
days ago, so npm skips them per `min-release-age=7`. The versions above
are the newest npm is allowed to install.

### oxfmt 0.48.0 → 0.73.0. Files changes would also have been needded on
upgrading Prettier 3.8.3 to 3.9.0

Formatting-only change: **150 files** reformatted (148 `.ts`, 2 `.md`).
These changes would eventually have been needed when upgrading Prettier
to 3.9.0 or above, even if this repo would not have migrated to oxfmt.

The TypeScript changes are embedded Lit template expressions. The layout
change was introduced by Oxfmt 0.61.0 through Oxc PR
[#24972](oxc-project/oxc#24972), “stop
preserving indent for embedded template expressions.” It was released in
Oxfmt 0.61.0.

The old Prettier output in this repository is explained by its locked
version, **3.8.3** at the time of #3871. Prettier 3.9.0 formats this
expression with the newer layout too. There was no config disabling it:
the pre-migration `.prettierrc` did not set
`embeddedLanguageFormatting`, so Prettier used its default `auto`
behavior.

```diff
-        ${gamePortal.isOnGamePortal()
-          ? this.renderPortalSignIn()
-          : this.renderLoginOptions()}
+        ${
+          gamePortal.isOnGamePortal()
+            ? this.renderPortalSignIn()
+            : this.renderLoginOptions()
+        }
```

No logic changes. The import-sorting config in `.oxfmtrc.json` is
unaffected: `sortImports` produces byte-identical output between 0.48.0
and 0.73.0 with this repo's config, so the ordering tuned in #6026 is
unchanged.

oxfmt is now about twice as fast: `oxfmt --check` over the repo went
from **4.26s** to **2.09s**.

### oxlint-plugin-eslint

`oxlint-plugin-eslint` is part of the official `oxc-project/oxc`
monorepo. It packages ESLint's own rule implementations as an Oxlint JS
plugin, which is the documented way to use rules Oxlint does not
implement natively yet. The follow-up
#6076 uses it to replace
`eslint/no-restricted-syntax`. Its version tracks Oxlint, so the
packages are bumped together.

## Verification

- `oxfmt --check` — clean
- Oxlint with the existing configuration — clean

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

tryout33

---------

Co-authored-by: Josh Harris <josh@openfront.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Verdict: No issues found. Checked for bugs and CLAUDE.md compliance. (0 critical / 0 major / 0 minor)

Full re-review of the PR at 37c89f8. Nothing new came up beyond the earlier Claude reviews:

  • .oxlintrc.json: correctness is now on, and the type-aware rules that ESLint never enforced are switched off. import/no-duplicates is enabled and .claude/** is ignored. The engine override uses the same Math.* list, the same ** selector and the same three engine package paths as the ESLint guard, with oxlint-plugin-eslint declared in package.json.
  • Removed side-effect imports (Main.ts GameModeSelector, SinglePlayerModal.ts, Store.ts, NavAccountMenu.ts, ProfileCard.ts, ReplayViewer.ts, plus the DesktopStatusBar, ReachabilityGating, NewsModal, ChatModal, EmojiTable and SpawnTimer tests): each module is still imported for a runtime value, such as joinIsGateable, savedGameIdFrom, alignPurchaseRows, levelBadgeLabel/levelBadgeAccent, ShowSettingsModalEvent, barSource, shouldBlockMultiplayerAction, new NewsModal(), ShowPlayerChatEvent, ShowPlayerEmojiMenuEvent and SpawnBarVisibleEvent. Because of that, the import isn't removed at build time and the custom element still registers.
  • Duplicate imports kept with suppressions (HostLobbyModal, InGameModal, LangSelector, UserSettingModal, Main.ts LangSelector/UsernameInput, ListLobbyDialog test): in each file the named import is only used as a type, so the bare side-effect import is still needed.
  • ?. → ! in tests: these are the no-unsafe-optional-chaining fixes. A missing value would fail the test either way; now it fails with a clearer error.

@Celant Celant 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.

Looks good. I checked that the DetMath restrictions still fire through the eslint plugin, and every dropped side-effect import still has a value import that registers the element.

@github-project-automation github-project-automation Bot moved this from Triage to Final Review in OpenFront Release Management Oct 23, 2026
@Celant
Celant merged commit a3e6484 into main Oct 23, 2026
16 checks passed
@Celant
Celant deleted the chore/oxlint-take-over-eslint branch October 23, 2026 10:20
@github-project-automation github-project-automation Bot moved this from Final Review to Complete in OpenFront Release Management Oct 23, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Oct 23, 2026
3 tasks done

This branch was successfully deployed

1 active and 1 inactive deployments
backport — 37c89f89 Deployed Oct 23, 2026 by Celant via Cherry-pick #547
staging — 37c89f89 Deployed Oct 23, 2026 by openfrontio[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

Sub-issue: follow-up to fully replace ESLint

3 participants