Repository navigation
feat!: migrate to ESLint 10 native flat config - #95
uPaymeiFixit wants to merge 22 commits into
Conversation
…^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>
| }, | ||
| "engines": { | ||
| "node": ">=18" | ||
| "node": "^20.19.0 || ^22.13.0 || >=24.0.0" |
There was a problem hiding this comment.
Should 23 be supported?
There was a problem hiding this comment.
No. This range is ESLint 10's own engines field. ESLint 10 doesn't support Node 23 (an odd-numbered release that's now end-of-life), and utils depends on ESLint 10 directly, so it couldn't run on 23 anyway.
That said, this question made me look at where else I'd changed engines. I've kept this range only on packages that depend on ESLint 10, and reverted it on the ones that don't (code-style, jest-configs, and the rubocop, stylelint, and typescript configs) in 9c1e95c.
| files: [ | ||
| '**/test/**', | ||
| '**/testing/**', | ||
| '**/__test__/**', | ||
| '**/*.test.*', | ||
| '**/*.spec.*', | ||
| '**/*.unit.*', | ||
| '**/*.e2e.*', | ||
| ], |
There was a problem hiding this comment.
Should this be included in the base config?
There was a problem hiding this comment.
I'd keep it out of base. A testing/ folder of test helpers is this repo's convention, and putting it in base would relax rules in any consumer's testing/ folder.
Also, this block isn't redundant with base, though it looks it. Base's testing override comes before the typescript layer, so typescript-eslint's strict configs re-enable the @typescript-eslint/* rules it turns off. Re-applying it here, last, is what makes those relaxations work for utils. v2's .eslintrc.yaml did the same thing for the same reason. The real fix is for eslint-config-typescript to own its test-file relaxations, but that changes what consumers get, so I'd rather do it as a follow-up than fold it into this migration.
I did switch this to defineConfig's extends, which reads like the original eslintrc override (8bbfb05, 6668a91).
There was a problem hiding this comment.
Update: I fixed the underlying problem instead of working around it. The testing and lenient relaxations for n/* and @typescript-eslint/* rules now live in eslint-config-node and eslint-config-typescript, so they come after the rules they relax. With that, utils doesn't need this block at all, so it's gone (b711e47).
| import base from '@code-style/eslint-config'; | ||
| import node from '@code-style/eslint-config-node'; | ||
| import esmodule from '@code-style/eslint-config-esmodule'; | ||
| import typescript from '@code-style/eslint-config-typescript'; | ||
| import testing from '@code-style/eslint-config/overrides/testing'; |
There was a problem hiding this comment.
do we need these to be default exports?
There was a problem hiding this comment.
No. Every config module now has a named config export, matching jest-configs:
import { config as base } from '@code-style/eslint-config';That also let me delete the three import/no-default-export exemptions the default exports needed. The remaining default exports are ones that have to be: the eslint.config.* files themselves, eslint-plugin-nest's plugin object, and type stubs for CJS plugins (8bbfb05).
There was a problem hiding this comment.
Maybe we should add a migration script to automate this
There was a problem hiding this comment.
Agreed. create-configs already does most of it: re-running it with overwrite writes eslint.config.mjs and removes the old .eslintrc* and .eslintignore. The gap is rules you've added to your own .eslintrc.yaml, which it drops. I'd rather teach create-configs to carry those over than add a separate script, but I'll do that in a follow-up so this PR stays focused. I've also cut this doc down to just the steps (8bbfb05).
There was a problem hiding this comment.
Update: done in create-configs instead of a separate script. When overwrite replaces an old .eslintrc, it now carries over the project's rules, ignore patterns, overrides' rules, and .eslintignore into eslint.config.mjs, and warns about anything it can't migrate (eg: env) (d1a1df0).
| "scripts": { | ||
| "build": "concurrently --group --prefix none \"npm:build:*\"", | ||
| "build:jsonschema": "typescript-json-schema src/config-types.ts CodeStyleRCFile > codestylerc.schema.json", | ||
| "build:jsonschema": "typescript-json-schema tsconfig.jsonschema.json CodeStyleRCFile > codestylerc.schema.json", |
There was a problem hiding this comment.
This looks sus. Why did we drop config-types.ts from the command? I get that we need to override the tsconfig for typescript-json-schema
There was a problem hiding this comment.
Good catch, it was a workaround. The command only broke because I'd regenerated the whole lockfile, which changed which version of @types/glob got hoisted. With the lockfile now rebuilt incrementally from main's (e434c1a), the original command works again, so I've restored it and deleted tsconfig.jsonschema.json (9c1e95c).
| **Only run this on `main` when you intend to release** — CI publishes any | ||
| pushed branch whose version is new (gated by the `production` environment | ||
| approval). |
There was a problem hiding this comment.
out of scope. look for more changes that are OOS
There was a problem hiding this comment.
Reverted. Looking for others turned up these, which I've also reverted:
- the
.nvmrcbump, the CI test matrix, and the e2e smoke test script - engines bumps on packages that don't depend on ESLint
- a
ts-jestpin in the publishedjest-configs, which would've downgraded its consumers - the
build:jsonschemaworkaround - a behavior change to
eslint-plugin-nest'sno-barreling(its suggestion deletes the whole file instead of just the export; that's a real bug, but it belongs in its own PR)
Several of these traced back to regenerating the lockfile from scratch, which upgraded every transitive dependency. It's now rebuilt incrementally from main's (e434c1a, 9c1e95c, 8bbfb05).
eslint-config-nest imports eslint-plugin-nest's types, and the concurrent workspace build raced it (every CI job failed with TS2307). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CONTRIBUTING.md, .nvmrc, the CI matrix, and the e2e smoke script - engines of packages that don't depend on ESLint - the ts-jest pin in jest-configs and the build:jsonschema workaround, both only needed because of the regenerated lockfile - committed *.tsbuildinfo build artifacts (now gitignored) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lockfile was regenerated from scratch, upgrading every transitive dependency and forcing ts-jest/type-fest workaround pins. Rebuild it from main's lockfile instead, updating only what the migration needs (the ESLint 10 plugin stack, plus TypeScript and @types/node, which eslint-plugin-n 18's types require). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- export `config` by name (matching jest-configs), dropping the import/no-default-export exemptions default exports needed - flat config tolerates `off` for unregistered plugins' rules, so move lenient/testing rules back to where they were in v2; json-files rules go back into footguns/readability - replace the hand-written ESLint 10 context shims with @eslint/compat's fixupPluginRules - fix: lint `.mjs` files (eg: eslint.config.mjs) as ES modules; CommonJS projects couldn't lint their own eslint.config.mjs - restore the original exports map shapes; ship d.ts files next to dist - drop dead code: unused deps, dead settings and ignores, the cli/lenient export, eslint-plugin-nest's recommended_flat alias - revert eslint-plugin-nest's no-barreling behavior change (follow-up) - examples and READMEs mirror the originals; trim the migration docs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like v2's .eslintrc.yaml: base's testing override is shadowed by the typescript layer, so utils re-applies it last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fresh checkouts inherited this incremental build state, so `tsc` skipped emitting eslint-config's declaration files and CI's type checks failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
typescript-eslint switches to single-run mode when CI=true, building its programs from files on disk. The tests lint in-memory code, so in CI it fell back to programs without the fixture's compiler options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The suggestion removed the whole Program, deleting the entire file instead of the export declaration it described. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base's testing and lenient configs turned off `n/*` and `@typescript-eslint/*` rules, but the node and typescript layers come after base and re-enabled them, so those relaxations never applied (in v2 either). Move them into eslint-config-node and eslint-config-typescript, which also makes utils' re-applied testing override unnecessary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Overwriting an old .eslintrc used to drop a project's own rules. Carry over its rules, ignore patterns, and overrides' rules, plus .eslintignore, and warn about anything else. Also fix chalk tagged templates mangling interpolated values in warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The publish action runs a bare `npm publish`, so every prerelease took `latest`. Set npm's tag from the version instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changing a `@typescript-eslint/*` or `jest/*` rule without scoping it to those configs' files fails with `Could not find plugin`, so export the globs to scope overrides with, and document how to customize the configs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- overlapping patterns (eg: `.prettierrc.mjs` and a regex matching it) could race to delete the same file; the second failure was swallowed, so setup deleted the old config without writing the new one - with remove off, an existing match was reported but ignored, so setup wrote a second config next to it (eg: `.prettierrc.mjs` next to `.prettierrc.json`) - `reject` never threw; drop the two editor checks that relied on that Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Migrates every
@code-style/eslint-config*package,eslint-plugin-nest, andcreate-configsfrom ESLint 8.eslintrcconfigs to ESLint 10 flat configs. Rule content is unchanged except where the plugin majors forced it.Context: Paciolan's Maps team wants to standardize on code-style, but it needs a current ESLint first (their spike rejected it over ESLint 8 / eslintrc). Frontend rules come in a follow-up.
What changes for consumers
configarray (likejest-configs); layering still works the same way, with later configs winning.-typescriptand-jestalso export theirfiles, for scoping your own changes to their rules; seedocs/customizing-eslint.md.^20.19.0 || ^22.13.0 || >=24. The packages are ESM-only, which also works for CommonJS projects since config files are loaded viaeslint.config.mjs.create-configsnow writeseslint.config.mjs, installseslintdirectly, and drops--ext. When overwriting an old.eslintrc, it carries over the project's own rules, ignore patterns, overrides' rules, and.eslintignore, and warns about anything it can't migrate.docs/migrating-v2-to-v3.md.Notable implementation details
eslint-plugin-import(no ESLint 10 support) is replaced witheslint-plugin-import-x, registered under theimportnamespace so everyimport/*rule ID and disable comment keeps working.eslint-plugin-react,eslint-plugin-jsx-a11y, andeslint-plugin-json-filesstill call rule-context methods ESLint 10 removed; they're wrapped with@eslint/compat'sfixupPluginRules. React & jsx-a11y also don't declare ESLint 10 in their peer ranges yet (see the migration guide).react-hooksis pinned torules-of-hooks+exhaustive-deps, since v6+'srecommendedadds the React Compiler rules..mjsfiles are linted as ES modules, so CommonJS projects can lint their owneslint.config.mjs.browser,cli,nest) are now built TS packages like the others.Fixes to v2 behavior
n/*and@typescript-eslint/*rules, but the node and typescript layers come after base and re-enabled them, so they never took effect. Each relaxation now lives in the package that owns the rule.no-barreling's suggestion deleted the whole file; it now deletes only theexport *declarations.overwriteoff an existing config was ignored, so setup wrote a second one next to it.next. The publish action runs a barenpm publish, so every prerelease tooklatest; the workflow now sets npm's tag from the version.Behavior changes forced by the upgrade
no-useless-assignment,no-unassigned-vars,preserve-caught-error, and others.no-throw-literal→only-throw-error).switch-exhaustiveness-checkkeeps v7's behavior of accepting adefaultcase.eslint-plugin-n18,eslint-plugin-jest29,eslint-plugin-promise7,@next/eslint-plugin-next16.Not in this PR
No version bump. With the workflow change, bumping to
3.0.0-Npublishes undernext, and existing^2.0.0-35ranges can't resolve to 3.x.🤖 Generated with Claude Code