Skip to content

chore: replace ESLint with oxlint - #2641

Open
joyenjoyer wants to merge 8 commits into
masterfrom
instui-5035-oxlint-migration
Open

chore: replace ESLint with oxlint#2641
joyenjoyer wants to merge 8 commits into
masterfrom
instui-5035-oxlint-migration

Conversation

@joyenjoyer

@joyenjoyer joyenjoyer commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add .oxlintrc.json (root + regression-test) porting the existing ESLint ruleset to oxlint's native plugins, its alpha jsPlugins bridge for 5 eslint-plugin-react rules oxlint lacks natively, and two custom rules (no-relative-imports, notice) reimplemented for oxlint's JS-plugin API
  • Wire oxlint into lint-staged, ui-scripts lint, and regression-test's lint script; remove ESLint's config files, its superseded custom plugin, and 11 now-unused dependencies. eslint-plugin-react and read-package-up are kept, since both are still runtime deps of oxlint's jsPlugins bridge and custom rules.
  • Fix 2 eslint-disable comments whose rule names didn't match oxlint's actual rule IDs and had silently stopped suppressing anything; delete 6 more that are genuinely dead under oxlint's role-has-required-aria-props implementation
  • Relocate ui-scripts' lint.ts out of a legacy lib/test/ folder into lib/commands/, alongside the other command modules

Test Plan

  • No manual verification needed; covered by existing tests. tsc -b, vitest, and a full-repo oxlint dry run all confirmed clean (only 5 pre-existing, unrelated no-undef errors remain in ui-icons's .cjs configs, unchanged from before this PR).

Fixes INSTUI-5035

🤖 Generated with Claude Code

@joyenjoyer joyenjoyer self-assigned this Jul 15, 2026
@joyenjoyer
joyenjoyer marked this pull request as ready for review July 15, 2026 13:44
@joyenjoyer
joyenjoyer force-pushed the instui-5035-oxlint-migration branch from 435bcbb to 65cacf4 Compare July 15, 2026 14:18
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://instructure.design/pr-preview/pr-2641/

Built to branch gh-pages at 2026-07-28 12:38 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Visual regression report

⚠️ Changes detected.

Status Count
Unchanged 0
Changed 1
New 96
Removed 32

📊 View full report

Diff images (33)

alert.png — baseline no longer produced

avatar.png — baseline no longer produced

badge.png — baseline no longer produced

billboard.png — baseline no longer produced

breadcrumb.png — baseline no longer produced

button-and-derivatives.png — baseline no longer produced

byline.png — baseline no longer produced

calendar.png — baseline no longer produced

checkbox.png — baseline no longer produced

checkboxgroup.png — baseline no longer produced

colorpicker.png — baseline no longer produced

contextview.png — baseline no longer produced

custom-and-lucide-icons.png — baseline no longer produced

dateinput-dateinput2.png — baseline no longer produced

datetimeinput.png — baseline no longer produced

diff-demo.png — 6324 pixels differ

drilldown.png — baseline no longer produced

filedrop.png — baseline no longer produced

form-errors.png — baseline no longer produced

heading.png — baseline no longer produced

img.png — baseline no longer produced

link.png — baseline no longer produced

menu.png — baseline no longer produced

metric-pill-tag-timeselect-text.png — baseline no longer produced

options.png — baseline no longer produced

pagination.png — baseline no longer produced

progressbar.png — baseline no longer produced

select-simpleselect.png — baseline no longer produced

table.png — baseline no longer produced

tabs.png — baseline no longer produced

tooltip.png — baseline no longer produced

treebrowser.png — baseline no longer produced

view.png — baseline no longer produced

Baselines come from the visual-baselines branch. They refresh on every merge to master.

github-actions Bot pushed a commit that referenced this pull request Jul 15, 2026
@joyenjoyer
joyenjoyer force-pushed the instui-5035-oxlint-migration branch 2 times, most recently from b1a8614 to 1429aaf Compare July 16, 2026 08:58
@joyenjoyer
joyenjoyer requested review from HerrTopi and balzss July 16, 2026 14:41

@balzss balzss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the changeset look ok and linting works fine for me, however this breaks my editor integration which showed realtime feedback about lint errors. i can work around it and find a plugin that fills this gap but it worths checking if other editors like webstorm and vscode support it or not

@matyasf
matyasf self-requested a review July 20, 2026 15:28

@matyasf matyasf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

While the commands work, I get no linting in WebStorm, it does not show me anything thats an error.
Pease add it to contributing-getting-started/Linters part on how to use it

Comment thread scripts/oxlint/notice-plugin.mjs Outdated
*/
export function hasNoticeHeader(sourceText) {
const leading = String(sourceText).replace(/\r\n/g, '\n').slice(0, CHARS)
return new RegExp(MUST_MATCH).test(leading)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why just match this short text? All text must be included

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Eslint-plugin-notice also scans the first 1000 characters by default that is why I implemented this way.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

But here here are comparing this the MUST_MATCH text, not 1000 characters

@joyenjoyer joyenjoyer Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed, full codebase linting became slower though.

Comment thread package.json
Comment thread .oxlintrc.json
"files": ["**/*"],
"excludeFiles": ["**/__tests__/**"],
"plugins": ["jsx-a11y"],
"rules": {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we really need such huge config for each plugin? Why not use some defaults?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am going to revisit all the rules in a separate PR as we discussed. Please accept this PR.

Comment thread .oxlintrc.json Outdated
Comment on lines +370 to +375
"files": ["packages/ui-truncate-text/src/TruncateText/__tests__/**"],
"plugins": ["vitest"],
"rules": {
"vitest/valid-expect": "off"
}
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why is there a rule for just this one?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This was added to bypass this oxlint rule flagging an old Chai style assertion, I updated these assertions in TruncateText test files and removed this rule bypass from the config file.

Comment thread .oxlintrc.json
"ts-ignore": "allow-with-description"
}
],
"@typescript-eslint/no-array-constructor": "error",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Cant we just use defaults for these? For most cases it would be much cleaner IMO

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am going to revisit all the rules in a separate PR as we discussed. Please accept this PR.

@HerrTopi
HerrTopi requested review from Copilot and removed request for HerrTopi July 22, 2026 10:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates the repo’s JavaScript/TypeScript linting from ESLint to oxlint, including porting previously custom ESLint rules to oxlint’s JS-plugin system and rewiring repo scripts to run oxlint instead of ESLint.

Changes:

  • Adds root and regression-test oxlint configs, including JS-plugins to replace the prior notice/notice and @instructure/no-relative-imports ESLint rules.
  • Updates scripts (lint-staged, ui-scripts lint, regression-test lint) to run oxlint, and removes the legacy ESLint config/custom plugin.
  • Cleans up stale eslint-disable directives and relocates the ui-scripts lint command module.

Reviewed changes

Copilot reviewed 23 out of 25 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scripts/oxlint/notice-plugin.mjs Adds an oxlint JS-plugin implementing the repo’s MIT notice header rule with an auto-fix.
scripts/oxlint/no-relative-imports-plugin.mjs Adds an oxlint JS-plugin to block cross-package relative imports (replacement for the removed ESLint plugin).
scripts/oxlint/node_tests/notice-plugin.test.ts Adds unit tests for notice plugin pure logic and rule visitor behavior.
scripts/oxlint/node_tests/no-relative-imports-plugin.test.ts Adds unit tests for no-relative-imports plugin pure logic and rule visitor behavior.
scripts/oxlint/node_tests/testfixtures/no-relative-imports/pkg-a/package.json Fixture package manifest for cross-package import tests.
scripts/oxlint/node_tests/testfixtures/no-relative-imports/pkg-a/src/index.ts Fixture file containing imports that should/shouldn’t be flagged.
scripts/oxlint/node_tests/testfixtures/no-relative-imports/pkg-a/src/sibling.ts Fixture sibling module import (allowed case).
scripts/oxlint/node_tests/testfixtures/no-relative-imports/pkg-b/package.json Fixture package manifest for the “other package” side of the test.
scripts/oxlint/node_tests/testfixtures/no-relative-imports/pkg-b/src/index.ts Fixture module imported via cross-package relative path (disallowed case).
scripts/eslint/no-relative-imports-plugin.mjs Removes the legacy ESLint custom plugin implementation.
regression-test/package.json Switches the regression-test lint script from ESLint to oxlint.
regression-test/.oxlintrc.json Adds a regression-test-specific oxlint config extending the root configuration.
packages/ui-tree-browser/src/TreeBrowser/v2/TreeCollection/index.tsx Removes now-dead ESLint disable comments for the a11y role rule.
packages/ui-tree-browser/src/TreeBrowser/v1/TreeCollection/index.tsx Removes now-dead ESLint disable comments for the a11y role rule.
packages/ui-scripts/package.json Replaces eslint peer dependency with oxlint.
packages/ui-scripts/lib/commands/lint.ts Updates ui-scripts lint to invoke oxlint rather than eslint.
packages/ui-scripts/lib/commands/index.ts Updates module import path after moving lint command into lib/commands/.
packages/ui-scripts/lib/node_tests/lint.test.ts Updates tests to assert oxlint invocation and new module path.
packages/ui-i18n/src/textDirectionContextConsumer.tsx Updates an eslint-disable rule id to match the oxlint/react-js rule namespace.
packages/ui-color-picker/src/ColorPreset/tests/ColorPreset.test.tsx Fixes trailing whitespace in a test title string.
packages/docs/globals.ts Updates disable comments to the new oxlint rule namespace for no-relative-imports.
package.json Removes ESLint dependencies and wires oxlint into lint-staged.
eslint.config.mjs Removes the repo’s ESLint flat config (superseded by oxlint configs).
.oxlintrc.json Adds the root oxlint configuration, including JS-plugin wiring and rule mappings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +127 to +130
const properImport = path.join(
importPkg.packageJson.name,
importBaseName === path.basename(importRoot) ? '' : importBaseName
)
ImportDeclaration(node) {
const message = checkImportForRelativePackage(
node.source.value,
context.filename
const [firstArgument] = node.arguments
const message = checkImportForRelativePackage(
firstArgument.value,
context.filename
@joyenjoyer
joyenjoyer requested a review from HerrTopi July 22, 2026 21:21
@joyenjoyer

Copy link
Copy Markdown
Contributor Author

@HerrTopi I need you back here :D, you are the main VSCode user in the team, please try the new Oxlint rules with this
plugin

@HerrTopi HerrTopi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems to be working for vscodium

joyenjoyer and others added 6 commits July 28, 2026 13:43
…older

Move lint.ts from lib/test/ into lib/commands/, alongside the other yargs
command modules (bump.ts, server.ts, etc.) it is registered with in
commands/index.ts. The test/ directory is a leftover name from when
ui-scripts absorbed the separate ui-test package.
Add .oxlintrc.json (workspace root and regression-test), porting the
ESLint ruleset to oxlint and covering gaps with two custom rules and a
jsPlugins bridge:

- react-js/* (jsPlugins bridge to eslint-plugin-react): covers no-deprecated, prop-types, no-typos,
  require-default-props, forbid-foreign-prop-types, missing from oxlint's native react plugin.
- instructure/no-relative-imports (custom): blocks relative imports that reach into another
  package's src, bypassing that package's public API.
- notice/notice (custom): requires the MIT license header at the top of source files.
- vitest/valid-expect (override, ui-truncate-text only): disabled because oxlint's vitest plugin
  does not recognize the chai-style assertions used in those tests.

Nothing consumes oxlint yet. ESLint still runs via lint-staged,
ui-scripts lint, and regression-test's lint script.
Wire oxlint into lint-staged, ui-scripts lint, and regression-test's
lint script, replacing ESLint everywhere it ran.

Remove ESLint's config files, its now-superseded custom plugin, and
every dependency with no remaining consumer: eslint, @eslint/js,
@vitest/eslint-plugin, eslint-config-prettier,
eslint-import-resolver-node, eslint-module-utils, eslint-plugin-compat,
eslint-plugin-jsx-a11y, eslint-plugin-notice, globals, and
typescript-eslint.

Keep eslint-plugin-react and read-package-up: both are still runtime
dependencies of oxlint's jsPlugins bridge and custom rules,
respectively.
Two disable comments reference rule names that do not match oxlint's
actual rule IDs, so they silently stopped suppressing anything:

- packages/__docs__/globals.ts: @instructure/no-relative-imports -> instructure/no-relative-imports.
  oxlint's custom rule has no @ scope prefix.
- packages/ui-i18n/src/textDirectionContextConsumer.tsx: react/forbid-foreign-prop-types ->
  react-js/forbid-foreign-prop-types. The bridged eslint-plugin-react is aliased react-js.

Delete 6 jsx_a11y/role-has-required-aria-props disable comments in
TreeBrowser's TreeCollection (v1 and v2):

- oxlint's rule does not require aria-selected for the treeitem role at all, unlike e.g. checkbox,
  which requires aria-checked, so there was nothing for these comments to suppress.

oxlint accepts both jsx-a11y/* and jsx_a11y/* spellings for disable
comments, so the unrelated jsx-a11y/* comments in Checkbox, FileDrop,
Img, NumberInput, and RadioInput needed no change.

Also trims a trailing space in one ColorPreset test title, an
unrelated vitest/valid-title auto-fix.
Replace the loose mustMatch: 'The MIT License' substring check with an exact
match against the full COPYRIGHT_NOTICE text. The old check only looked for
that one phrase in the first 1000 characters, so a header with the phrase
present but other lines altered, wrong, or missing was accepted as valid.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Correct license headers that drifted from the canonical text and were
missed by the old loose oxlint notice check: wrong copyright start years
(2018/2021 instead of 2015) in ui-date-time-input, ui-editable, ui-i18n and
ui-tree-browser, a "copyrigfht" typo in ui-color-picker, and an extra
period after "(MIT)" in vitest.config.mts, vitest.setup.ts,
vitest.setup.browser.ts, and a ui-portal test file.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@joyenjoyer
joyenjoyer force-pushed the instui-5035-oxlint-migration branch from 1429aaf to 527cc7d Compare July 28, 2026 12:03
github-actions Bot pushed a commit that referenced this pull request Jul 28, 2026
joyenjoyer and others added 2 commits July 28, 2026 14:23
Replace expect(...).to.equal(...) chai-style assertions with vitest's
native toEqual() in cleanString.test.tsx and cleanData.test.tsx. These
were leftover from an old Mocha/Chai migration and were the only
reason oxlint's vitest/valid-expect rule was disabled for this
package's tests. Remove that now-unneeded per-file override from
.oxlintrc.json so the rule applies uniformly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Update the Linters and Code Formatting section to reflect the ESLint
to oxlint migration: list the lint/lint:fix/lint:changes commands and
swap the ESLint/Stylelint VSCode plugin recommendation for the Oxc
extension.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Jul 28, 2026
github-actions Bot pushed a commit that referenced this pull request Jul 28, 2026
@joyenjoyer
joyenjoyer requested a review from matyasf July 28, 2026 12:50
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.

5 participants