Skip to content

Update eslint configuration to use new flat config instead of rushstack #12198

@rtibbles

Description

@rtibbles

This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Overview

Migrate from the legacy .eslintrc configuration (with @rushstack/eslint-patch for module resolution) to ESLint 9's flat config format (eslint.config.mjs). This removes the rushstack workaround entirely, as flat config resolves plugins via direct imports — the exact problem rushstack was patching around.

Complexity: Medium
Target branch: develop

Context

The project currently uses @rushstack/eslint-patch/modern-module-resolution in the root .eslintrc.js to work around ESLint's legacy plugin resolution issues. The actual ESLint configuration lives in packages/kolibri-format/.eslintrc.js and is loaded programmatically by packages/kolibri-format/index.js using the ESLint Node.js API (new ESLint({ baseConfig, resolvePluginsRelativeTo })).

Key files involved:

  • .eslintrc.js (root) — requires rushstack patch, delegates to kolibri-format
  • packages/kolibri-format/.eslintrc.js — all rules, plugins, extends
  • packages/kolibri-format/index.js — programmatic ESLint API usage
  • packages/eslint-plugin-kolibri/ — custom lint rules (CJS, exports rules object)

kolibri-format is also consumed by projects outside this monorepo that may provide their own ESLint config, so the host-project config lookup pattern must be preserved.

Original issue context

The issue that motivated rushstack/eslint has some recent updates:

Hi folks! Just an update that the new ESLint configuration has now been enabled as an experiment. This new config system eliminates the special treatment of shared configs so that you can include plugins as direct dependencies.

Originally posted by @MisRob in #9698 (comment)

The Change

Upgrade from ESLint 8 to ESLint 9 and convert the configuration from legacy .eslintrc format to flat config (eslint.config.mjs).

Key changes:

  • Replace .eslintrc.js files with eslint.config.mjs files using ESM imports
  • Replace eslint-plugin-import with eslint-plugin-import-x (the maintained fork with native flat config support)
  • Update packages/kolibri-format/index.js to load the new config via await import() and use ESLint 9's API (which removes resolvePluginsRelativeTo — no longer needed since flat config resolves plugins via direct imports)
  • Remove @rushstack/eslint-patch from root package.json
  • Strict 1:1 rule parity — no rule additions, removals, or severity changes. The only rule name changes are the mechanical import/import-x/ rename.

eslint-plugin-kolibri and eslint-plugin-small-import need no changes — their CJS rules exports work as flat config plugins as-is.

Out of Scope

  • Changing, adding, or removing any lint rules (beyond the import/import-x/ rename)
  • Modernizing eslint-plugin-kolibri to ESM
  • Converting kolibri-format/index.js to ESM
  • Upgrading other linting tools (prettier, stylelint)

Acceptance Criteria

General

  • @rushstack/eslint-patch is removed from root package.json
  • Root .eslintrc.js is replaced by eslint.config.mjs
  • packages/kolibri-format/.eslintrc.js is replaced by eslint.config.mjs
  • packages/kolibri-format/index.js uses ESLint 9 API with await import() for config loading
  • Host-project config lookup is preserved (check for eslint.config.mjs in host project, fall back to own)
  • eslint-plugin-import is replaced by eslint-plugin-import-x
  • All existing lint rules produce identical results — no new warnings or errors introduced

Testing

  • pre-commit run lint-frontend --all-files output is identical before and after migration
  • eslint-plugin-kolibri tests pass
  • Manual smoke test: deliberate import-x/ rule violations (duplicate imports, wrong order, missing newline after imports) are caught

References

AI usage

This issue was restructured from a rough initial description using Claude Code. The design decisions (ESLint version, config format, plugin choices, migration approach) were made collaboratively through iterative Q&A, drawing on findings from a previous contributor's implementation attempt documented in the issue comments. The acceptance criteria and scope were reviewed and confirmed by a human maintainer.

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions