Skip to content

Upgrade to ESLint 10 and @cloudfour/eslint-config v26.1.0 - #2467

Merged
spaceninja merged 1 commit into
mainfrom
eslint-10-upgrade
Sep 1, 2026
Merged

Upgrade to ESLint 10 and @cloudfour/eslint-config v26.1.0#2467
spaceninja merged 1 commit into
mainfrom
eslint-10-upgrade

Conversation

@spaceninja

Copy link
Copy Markdown
Member

Overview

Supersedes #2458, which Renovate opened for v26.0.0 and which could not install. With #2466 merged the eslint-plugin-react blocker is gone, and this takes us the rest of the way: ESLint 10.9.1 and @cloudfour/eslint-config v26.1.0.

This does not need TypeScript 6, so #2465 can stay parked. xo declares typescript: ">=6", but that peer is optional. On 5.9.3 the install resolves, npm ci works, and typescript-estree resolves our root TypeScript rather than the copy npm records for xo. That matters more than it sounds: on TypeScript 6 the type-aware rules here report nonsense — while (scrollHeight > clientHeight) comes back as "always falsy" — and no-unnecessary-condition is autofixable, so npm run lint:js would delete the guards it misreads. The details are in #2465.

v26 reports 405 findings on this repo. Most were not really 405 problems. Two scoped rules account for 256 of them, and neither is a shrug:

  • camelcase (226) — every snake_case identifier involved belongs to someone else's naming scheme. Twig template variables in stories and arg files are the template's contract; design token keys become CSS custom properties and Sass variables in our published output; demo data mirrors WordPress's paginate_links(); and one is a terser option. Renaming any of them breaks something.
  • jsdoc/require-description (30)checkJs is on, so JSDoc is how our .js files carry types. Demanding a prose description above every /** @param {Args} args */ produces filler, not documentation.

The remaining ~150 are ordinary fixes: the v flag on regexes, unused capture groups made non-capturing, named default exports, node:process imports, declarations moved below early exits, and leading slashes dropped from files patterns. Four Twig imports were renamed to camelCase to match the 114 that already are. terser becomes a declared devDependency instead of one we relied on reaching us through the rollup plugin.

Three that would have been wrong to just fix

Worth flagging, because in each case the rule's suggested change breaks something:

  1. non-nullable-type-assertion-style autofixed closest('.js-sky-nav') as HTMLElement into closest('.js-sky-nav')!. Those are not equivalent — ! only removes the null and leaves Element, while offsetWidth lives on HTMLElement. tsc went from clean to one error. The generic form (closest<HTMLElement>(…)!) satisfies the rule and keeps the type.
  2. prefer-number-coercion wanted Number() in place of Number.parseFloat() on tokens.time.transition.slow.value. That value is "0.4s", so Number() yields NaN and the sky nav's transition becomes transform NaNs. Left as parseFloat, with the reason in a comment.
  3. require-unicode-regexp is off in browser tests. Regexes handed to Vitest locators are serialised into a selector string rather than executed, and its parser rejects the flag outright — Error while parsing selector button[name=/^reply$/iv]. Adding it turned two passing tests into failures.

package-json/prefer-side-effects-field and require-types-in-exports are off with a note rather than acted on. Both describe what we publish and both break consumers quietly when wrong — an inaccurate sideEffects lets bundlers drop our styles — so they deserve a deliberate look at packaging rather than a change smuggled into a lint upgrade.

Lockfile

Updated by removing the old config and installing the new one, rather than regenerating. Regenerating from scratch changed 226 package versions, 56 of them major, including a @vitest/* 3→4 bump and a @types/node downgrade from 26 to 22. This way it is 36 changes, 13 major, every one in the ESLint ecosystem.

Screenshots

Testing

  • Run npm ci, then npm run preprocess, then npm run lint:check — passes with no output
  • Run npm run type and npm run build — both succeed
  • Run npm test — 12 test files, 25 tests, all passing. The two browser tests in comment and sky-nav are the ones the regex flag broke, so they are the ones worth seeing green
  • Run npm start and open the Sky Nav story. Toggle the menu open and closed at a narrow viewport — it should slide smoothly rather than snapping, which is what a NaN transition duration would look like
  • In the same Storybook, open Components → Subscribe and click "Get Weekly Digests", then press Escape — the form should hide again
  • Open Components → Input → Elastic Textarea and type several lines — the textarea should grow as you go
  • Open a few component docs pages (Alert, Card, Button) and confirm the Canvas, Controls and props tables all render
  • Open Vendor → WordPress → Core Element Comparison — all four demos should still render, since their imports were renamed

Closes #2458
Related: #2465 (parked — TypeScript 6 is not needed for this)

This does not need TypeScript 6. xo peers `typescript: ">=6"`, but that
peer is optional: on 5.9.3 the install resolves, `npm ci` works, and
`typescript-estree` resolves our root TypeScript rather than the copy npm
records for xo. That matters, because TypeScript 6 makes the type-aware
rules report nonsense here -- `while (scrollHeight > clientHeight)` as
"always falsy" -- and their autofix would delete the guards it misreads.
See #2465, which stays parked.

405 findings became zero. Most of them went through two scoped rules
rather than edits to source:

- `camelcase` (226) is off where the snake_case names are someone else's:
  Twig template variables in stories and arg files, design token keys that
  become CSS custom properties, mock data shaped like WordPress's
  `paginate_links()`, and terser options.
- `jsdoc/require-description` (30) is off for .js files. With `checkJs`
  on, JSDoc is how those files carry types, and requiring prose above
  every `/** @PARAM {Args} args */` produces filler.

Also renames `import/order` to `import-x/order`, which v26 documents.

Three things needed care rather than the obvious fix:

- `non-nullable-type-assertion-style` autofixed `closest('.js-sky-nav')
  as HTMLElement` into `closest('.js-sky-nav')!`, which drops the
  narrowing -- `!` yields `Element`, and `offsetWidth` is on
  `HTMLElement`. `tsc` went from clean to one error. The generic form
  satisfies the rule and keeps the type.
- `prefer-number-coercion` wanted `Number()` instead of
  `Number.parseFloat()` on `tokens.time.transition.slow.value`. That value
  is "0.4s", so `Number()` gives NaN and the transition becomes
  `transform NaNs`. Left as `parseFloat` with the reason recorded.
- `require-unicode-regexp` is off in browser tests. Regexes passed to
  Vitest locators are serialised into a selector string rather than
  executed, and its parser rejects the `v` flag outright, which turned two
  passing tests into failures.

The rest are ordinary: the `v` flag elsewhere, unused capture groups made
non-capturing, named default exports, `node:process` imports, leading
slashes dropped from `files` patterns, four Twig imports renamed to match
the 114 that already use camelCase, and declarations moved below early
exits. `terser` is now a declared devDependency rather than one we relied
on arriving through the rollup plugin.

`sideEffects` and `require-types-in-exports` are off with a note. Both
describe what we publish, and both break consumers quietly when wrong, so
they deserve a deliberate look at packaging rather than a change here.

The lockfile was updated by removing the old config and installing the
new one. Regenerating it from scratch instead changed 226 package
versions including a major vitest bump and a `@types/node` downgrade;
this way it is 36 changes, 13 major, all in the ESLint ecosystem.

Verified: lint, `tsc`, build, and 25/25 tests.

Closes #2458
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: da74529

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify

netlify Bot commented Sep 1, 2026

Copy link
Copy Markdown

Deploy Preview for cloudfour-patterns ready!

Name Link
🔨 Latest commit da74529
🔍 Latest deploy log https://app.netlify.com/projects/cloudfour-patterns/deploys/6a9615aabfc1b40008458d72
😎 Deploy Preview https://deploy-preview-2467--cloudfour-patterns.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@spaceninja
spaceninja merged commit 39a80f0 into main Sep 1, 2026
8 checks passed
@spaceninja
spaceninja deleted the eslint-10-upgrade branch September 1, 2026 00:14
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.

1 participant