Skip to content

feat!: migrate to ESLint 10 native flat config - #95

Open
uPaymeiFixit wants to merge 9 commits into
mainfrom
feat/eslint-10-flat-config
Open

feat!: migrate to ESLint 10 native flat config#95
uPaymeiFixit wants to merge 9 commits into
mainfrom
feat/eslint-10-flat-config

Conversation

@uPaymeiFixit

Copy link
Copy Markdown
Collaborator

Migrates every @code-style/eslint-config* package from ESLint 8 / .eslintrc to native ESLint 10 flat config, plus the scaffolder, test harness, self-linting, CI, and docs. Rule content is preserved except where the forced plugin majors changed it (audited — see below).

Context: Paciolan's Maps team is standardizing code style and is willing to adopt code-style if it's on a current ESLint (their spike rejected it primarily over ESLint 8 EOL / eslintrc). This unblocks that, and a follow-up will add the frontend layers (tanstack-query etc.).

Headline decisions

  • ESLint 10 only (peer ^10.0.0), Node engines ^20.19.0 || ^22.13.0 || >=24.
  • ESM-only packages ("type": "module", esbuild --format=esm). Verified safe for CJS consumers: configs are only loaded through eslint.config.mjs, and Node ≥20.19 require(esm) covers the rest (smoke-tested from a "type": "commonjs" project).
  • Packages export plain Linter.Config[] arrays; consumers compose with defineConfig. Layering philosophy unchanged — later entries still win.
  • eslint-plugin-importeslint-plugin-import-x, registered under the import namespace so every import/* rule ID and inline disable keeps working. TS resolution via resolver-next + eslint-import-resolver-typescript.
  • YAML packages (-browser, -cli, -nest) converted to built TS packages; -cli gains the ./lenient export create-configs was already emitting (previously a dead subpath).
  • @rushstack/eslint-patch deleted everywhere (plugins are real imports now).
  • react-hooks bumped to v7 but pinned to the classic pair (rules-of-hooks/exhaustive-deps) — adopting the React Compiler rule set should be a deliberate versioned change.
  • create-configs emits eslint.config.mjs, installs eslint as a direct devDependency, drops --ext, and removes legacy eslintrc files + .eslintignore on overwrite.

ESLint 10 compat shims (temporary)

eslint-plugin-react, eslint-plugin-jsx-a11y, and eslint-plugin-json-files still call rule-context methods ESLint 10 removed (context.getFilename() etc.), and the first two still cap their peer ranges at ESLint 9. Small prototype-chain shims wrap their rules (eslint-config-react/src/compat.ts, eslint-config/src/overrides/json.ts) — delete when upstream catches up. The json-files processor also gets the meta object v10's --cache requires.

Behavior deltas (from the pre/post --print-config audit)

Snapshotted effective configs for js/ts/test/json/tsx files before and after; every delta traces to a documented upstream change:

  • ESLint 10 recommended additions (no-useless-assignment, preserve-caught-error, …)
  • typescript-eslint 8 renames/additions (ban-types split, no-throw-literalonly-throw-error, …); switch-exhaustiveness-check pinned to v7 semantics
  • n 17→18 and next 14→16 recommended-set changes

The audit also caught (and this PR fixes) one would-be regression: tseslint v8's flat eslint-recommended disables core rules like no-undef universally, where v7's was TS-scoped — it's re-scoped to TS files.

One deliberate fix in eslint-plugin-nest: the no-barreling suggestion now removes the offending export * from declaration instead of the whole Program (v10 widened Program.range to include comments, which would have made the old fix worse).

Verification

  • Build clean; dist confirmed ESM. Repo self-lints with its own flat configs.
  • 132 node:test cases + 6 RuleTester cases + 50 scaffolder jest specs green.
  • New CI: Node 20.19/22/24 test matrix + an e2e job (scripts/e2e-create-configs.sh) that packs all workspaces into a throwaway verdaccio registry, scaffolds a sample project non-interactively, and proves the generated config catches a seeded violation and passes a clean file.
  • External tarball smoke in a "type": "commonjs" project incl. require('@code-style/eslint-config').

Not in this PR (deliberate)

  • No version bump. Per the updated CONTRIBUTING.md: when releasing, bump.ts premajor → 3.0.0-N with "publishConfig": { "tag": "next" } added to every workspace in the same commit, so prereleases don't capture latest (the publish action runs a bare npm publish). ^2.0.0-35 consumers can never resolve 3.x, so the existing fleet is safe.
  • Frontend gap-fill (tanstack-query layer, unused-imports/simple-import-sort, prettier reconciliation) — follow-up.

Migration guide for consumers: docs/migrating-v2-to-v3.md.

🤖 Generated with Claude Code

uPaymeiFixit and others added 9 commits August 10, 2026 22:11
…^10, engines node >=20.19, lockfile regen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fix to ExportAllDeclaration

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- YAML packages (browser/cli/nest) converted to built TS packages
- eslint-plugin-import → eslint-plugin-import-x registered under the import namespace
- typescript-eslint 8, eslint-plugin-n 18, jest 29, promise 7, react-hooks 7 (classic pair pinned)
- foreign-rule relaxations relocated to their owning packages
- cli gains an empty ./lenient export
- json-files rules consolidated into one **/*.json config with explicit processor

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… delete all .eslintrc.yaml

Also: ESLint 10 context-method shims for json-files/react/jsx-a11y plugins,
import-x resolver-next wiring, type-aware linting disabled for config files,
eslint.config.* exempted from import/no-default-export.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- initESLint uses overrideConfigFile:true + flat overrideConfig; tmp-dir tests get cwd-scoped linters
- utils workspace goes ESM with a typed exports map (fixes Node 24 interop + phantom any types)
- test configs import the flat arrays instead of eslintrc extends strings
- base covers the full JS/TS file family (parity with the old --ext ts,js,json behavior)
- json-files processor gains the meta object ESLint 10's --cache requires
- switch-exhaustiveness-check pinned to v7 semantics (considerDefaultExhaustiveForUnions)
- test snippets updated for eslint 10 / typescript-eslint 8 forced rule deltas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v7's eslintrc version only applied to *.ts; v8's flat version is universal,
which silently turned off no-undef & friends for plain JS. Also pin
switch-exhaustiveness-check to v7 semantics. Found via pre/post
--print-config diff audit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- flat-config imports + defineConfig in today's exact layering order
- eslint installed as a direct devDependency (it's a peer of the config packages now)
- lint:js drops --ext (files globs govern extensions); uninstall list gains
  import-x/@types/eslint/@rushstack-eslint-patch; removal set covers legacy
  eslintrc files and .eslintignore
- specs assert the import list and defineConfig call; new cli+lenient case

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… release/dist-tag policy

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant