Skip to content

[lexical] Chore: Upgrade ESLint 8 to ESLint 10 with flat configuration#8287

Merged
etrepum merged 21 commits intofacebook:mainfrom
etrepum:claude/upgrade-eslint-10-9xJI9
Apr 7, 2026
Merged

[lexical] Chore: Upgrade ESLint 8 to ESLint 10 with flat configuration#8287
etrepum merged 21 commits intofacebook:mainfrom
etrepum:claude/upgrade-eslint-10-9xJI9

Conversation

@etrepum
Copy link
Copy Markdown
Collaborator

@etrepum etrepum commented Apr 6, 2026

Description

The monorepo was using ESLint 8.57.0 with the legacy .eslintrc.js configuration format, which is no longer supported in ESLint 10. This PR migrates the entire ESLint setup to ESLint 10 with the modern flat configuration format, and upgrades related tooling.

(authored with claude)

ESLint migration:

  • Replace .eslintrc.js and .eslintignore with eslint.config.mjs (ESM flat config)
  • Upgrade eslint from ^8.57.0 to ^10.0.0
  • Replace eslint-config-fbjs with @eslint/js recommended + inlined rules (fbjs is abandoned, last published July 2022, no flat config support)
  • Replace eslint-plugin-import with eslint-plugin-import-x (ESLint 10 compatible)
  • Replace separate @typescript-eslint/parser + @typescript-eslint/eslint-plugin with unified typescript-eslint package (^8.58.0, explicit ESLint 10 support)
  • Add @eslint/compat to wrap legacy plugins that use removed ESLint APIs (eslint-plugin-react, eslint-plugin-header, eslint-plugin-sort-keys-fix, eslint-plugin-no-function-declare-after-return)
  • Add globals package to replace env configuration
  • Configure no-console to allow warn, error, time, timeEnd (matching prior effective behavior — only console.log is flagged)
  • Adopt ESLint 10 defaults for no-constant-condition (while(true) allowed) and no-inner-declarations (block-scoped functions allowed)
  • Add react/jsx-key rule and fix two pre-existing missing key prop errors
  • Remove stale eslint-disable directives for rules whose defaults changed
  • Fix 135 unnecessary escape characters in e2e test template literals
  • Update Node engine requirement to >=20.19.0 (ESLint 10 requirement)

ESLint plugin changes:

  • Raise peerDependencies.eslint from >=7.31.0 to >=9 for @lexical/eslint-plugin-internal (private, monorepo only)
  • Refactor @lexical/eslint-plugin-internal from CJS to ESM
  • Fix no-imports-from-self rule to resolve monorepo root dynamically instead of using a fragile relative path that broke under pnpm's hardlink structure
  • Refactor rules-of-lexical.js and getParentAssignmentName.js to pass type checking with ESLint 10's stricter Node.parent nullability
  • Retain getSourceCode() fallback in rules-of-lexical.js for ESLint 8 backwards compatibility
  • Update RuleTester config in unit tests to flat config format
  • Import @lexical/eslint-plugin by direct file path in eslint.config.mjs (bypasses package.json exports map which points to non-existent built files in development)

Module system modernization:

  • Refactor .lintstagedrc.js to .lintstagedrc.mjs (ESM)

Dependency upgrades:

  • Upgrade eslint-config-prettier from ^9.1.0 to ^10.0.0
  • Upgrade eslint-plugin-react from ^7.34.1 to ^7.37.5
  • Upgrade eslint-plugin-react-hooks from ^4.6.2 to ^5.2.0
  • Upgrade eslint-plugin-jsx-a11y from ^6.8.0 to ^6.10.2
  • Upgrade eslint-plugin-no-only-tests from ^3.1.0 to ^3.3.0
  • Upgrade eslint-plugin-simple-import-sort from ^12.1.0 to ^13.0.0
  • Upgrade prettier from ^3.6.2 to ^3.8.1 (pinned via pnpm override)
  • Upgrade lint-staged from ^11.1.0 to ^16.0.0
  • Upgrade husky from ^7.0.1 to ^9.1.7

Dependencies removed:

  • eslint-config-fbjs, eslint-plugin-babel, eslint-plugin-ft-flow, eslint-plugin-import, @typescript-eslint/parser, @typescript-eslint/eslint-plugin, @babel/eslint-parser, @types/prettier

Test plan

  • pnpm run ci-check passes (TypeScript, Flow, Prettier, ESLint)
  • pnpm run test-unit — all 104 test suites pass (2737 tests)
  • pnpm run test-eslint-integration — all 6 integration tests pass (ESLint 8 legacy fixtures + ESLint 10 flat config fixtures)
  • lint-staged pre-commit hook passes (verified by successful commits)

- Replace .eslintrc.js and .eslintignore with eslint.config.mjs (ESM flat config)
- Upgrade eslint from ^8.57.0 to ^10.0.0
- Replace eslint-config-fbjs with @eslint/js recommended + inlined rules
- Replace eslint-plugin-import with eslint-plugin-import-x (ESLint 10 compat)
- Replace @typescript-eslint/parser + @typescript-eslint/eslint-plugin with
  unified typescript-eslint package (^8.58.0, supports ESLint 10)
- Add @eslint/compat to wrap legacy plugins (react, header, sort-keys-fix, etc.)
- Add globals package to replace env configuration
- Upgrade eslint-plugin-react-hooks from ^4.6.2 to ^5.2.0
- Upgrade eslint-plugin-react from ^7.34.1 to ^7.37.5
- Upgrade eslint-config-prettier from ^9.1.0 to ^10.0.0
- Remove eslint-config-fbjs, eslint-plugin-babel, eslint-plugin-ft-flow
  (no longer needed - project has migrated from Flow to TypeScript)
- Remove @babel/eslint-parser (default espree parser handles all JS files)
- Fix no-imports-from-self rule to resolve monorepo root dynamically
  instead of using fragile relative path
- Fix unnecessary escape characters in e2e test template literals
- Remove unused eslint-disable directives
- Update Node engine requirement to >=20.19.0 (ESLint 10 requirement)

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 6, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Apr 7, 2026 5:14am
lexical-playground Ready Ready Preview, Comment Apr 7, 2026 5:14am

Request Review

- Convert all source files from CommonJS (require/module.exports) to
  ES modules (import/export)
- Add "type": "module" and "exports" field to package.json
- Remove separate rules/index.js barrel file, inline into src/index.js
- Replace __dirname with import.meta.url + fileURLToPath
- Use createRequire for loading CJS dependency (PackageMetadata.js)
- Simplify no-optional-chaining rule to use context.sourceCode directly
  (ESLint 10 only, no legacy context.getSourceCode() fallback needed)
- Update eslint.config.mjs to use ESM import for internal plugin

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
- Revert auto-removal of eslint-disable comments by eslint --fix
- Keep no-console strictly enabled (no {allow}), use per-line disables
- Add eslint-disable-next-line no-console to source files that
  intentionally use console.warn/console.error
- Restore no-constant-condition with {checkLoops: 'all'} to match
  ESLint 8 behavior (ESLint 10 defaults to allExceptWhileTrue)
- Add no-shadow override for TS files with ignoreTypeValueShadow: false
  to match old config behavior
- Disable strict rule for .mjs files (irrelevant for ESM)
- Keep devtools/playground no-console: OFF override for pre-existing usage

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
…quire

ESM import can natively load CJS modules via direct file paths, bypassing
the package.json exports map (which points to non-existent built .mjs files
in development). This removes one of the two createRequire usages.

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
- Set reportUnusedDisableDirectives: 'off' to prevent eslint --fix from
  stripping existing eslint-disable comments for rules that may not be
  active in the current config (stale guards, @typescript-eslint
  type-checked rules, proactive disables)
- Restore no-inner-declarations with blockScopedFunctions: 'disallow'
  to match ESLint 8 behavior (ESLint 10 defaults to 'allow')
- Remove blanket no-console: OFF overrides for devtools/playground;
  use per-line eslint-disable-next-line no-console instead
- Add eslint-disable-next-line no-console to all source files that
  intentionally use console.warn/console.error
- Add react/jsx-key rule (from react recommended config) and fix
  two pre-existing missing key prop errors
- Restore all eslint-disable directives that were incorrectly stripped
  by eslint --fix in earlier commits

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
claude added 2 commits April 6, 2026 17:29
…directive warnings

- Change no-console to [error, {allow: ['warn', 'error']}] to match
  the prior effective behavior (console.log is flagged, warn/error are not)
- Remove no-constant-condition override, use ESLint 10 default
  (checkLoops: 'allExceptWhileTrue' — while(true) is now allowed)
- Remove no-inner-declarations override, use ESLint 10 default
  (blockScopedFunctions: 'allow' — block-scoped functions are now allowed)
- Set reportUnusedDisableDirectives back to 'warn' (ESLint 10 default)
- Remove now-stale eslint-disable directives for no-console (console.warn
  and console.error are allowed), no-constant-condition (while(true) is
  allowed), and no-inner-declarations (block-scoped functions are allowed)

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
- Restore files from main where the only diff was empty lines left behind
  by eslint --fix stripping unused directives
- Allow console.time and console.timeEnd in no-console config
- Restore inline comments in isQuestionDotToken explaining espree compat
- Remove now-unnecessary eslint-disable comments for console.time/timeEnd

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
These comments were left behind after the eslint-disable-next-line
react-hooks/exhaustive-deps directives they accompanied were removed.

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
The e2e tests use prettier's HTML parser at runtime (prettifyHTML).
pnpm was resolving prettier@3.6.2 for some packages due to stale
lockfile entries, causing 'Missing visitor keys' errors in the HTML
parser. Pin prettier to 3.8.1 via pnpm overrides to ensure a single
version is used throughout the monorepo.

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
Refactor rules-of-lexical.js and getParentAssignmentName.js to pass
type checking with ESLint 10's stricter types:
- Add null guards for Node.parent (nullable in ESLint 10's type defs)
- Cast Definition.node to Rule.Node to access the parent property
- Revert @ts-nocheck additions

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
The published @lexical/eslint-plugin may still be used with ESLint 8
by external consumers, and the integration tests verify this. Restore
the context.sourceCode || context.getSourceCode() fallback with a
@ts-expect-error for the removed type.

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
This is an externally published package — keep the broad peer dep range
to avoid breaking existing consumers on older ESLint versions.

https://claude.ai/code/session_01DxtHyfC26VYrRm1YKsWyTP
@etrepum etrepum added this pull request to the merge queue Apr 7, 2026
Merged via the queue into facebook:main with commit 3ddfc75 Apr 7, 2026
62 of 63 checks passed
@etrepum etrepum mentioned this pull request Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants