Repository navigation
Releases: cloudfour/eslint-config
Release list
v29.0.0
Major Changes
-
eslint-config-xois now v4, which bringseslint-plugin-unicorn76 and
eslint-package-json1.0. Ten rules arrive at error severity:unicorn/no-async-iterator-callbackunicorn/no-unused-builtin-method-return, which replaces the now-deprecated
unicorn/no-unused-array-method-returnand covers more built-ins. A config
that turns the old name off still loads, but should switch to the new one.unicorn/no-unused-iterator-helperunicorn/no-useless-set-constructionunicorn/no-using-resource-escapeunicorn/prefer-combined-guardsunicorn/prefer-iterator-zipunicorn/prefer-temporal-conversionpackage-json/no-absolute-paths-in-scriptspackage-json/no-incompatible-peer-dependency-rangespackage-json/require-bin-executable
Several rules we already had also report more than before.
unicorn/prefer-ternary,prefer-early-returnandprefer-continuenow
recognise more code shapes. The last two also gain acheckShortBodies
option, andno-break-in-nested-loopandno-immediate-mutationgain options
of their own, but every new option defaults to off, so the extra reports come
from the broader matching, not the options.Measured across 855 files in three consumers, this is 21 new reports, 13 of
them auto-fixable. Ten areprefer-ternary, all fixable; ten more are
prefer-early-returnandprefer-continue, of which only two are fixable; the
last is one fixableprefer-combined-guards. None of the other new rules
reported anything, apart from onerequire-bin-executablereport that came
only from abinpointing into an unbuiltdist/. -
The supported TypeScript range is now declared:
>=6.0.0 <6.1.0. It is an
optional peer dependency, so JavaScript-only projects are unaffected. The
range was always there, set by whattypescript-eslintandeslint-config-xo
support, but nothing said so, and projects on other versions found out the
hard way. On TypeScript 5.x, npm quietly installed a second, unused
typescript@7inside this package. On TypeScript 7, install printed a stream of
peer warnings, and linting failed later.Now, installing or upgrading this package in a project whose TypeScript is
outside the range fails immediately with anERESOLVEerror that names
@cloudfour/eslint-configand the range it accepts. To upgrade, move the
project to TypeScript 6.0 first. We will widen the range in the same release
thattypescript-eslintsupports a newer TypeScript.This does not stop a project already on this version from bumping TypeScript
itself. npm treats a change to the project's owntypescriptas deliberate
and only warns, so a Renovate PR offering TypeScript 7 still opens and fails at
lint or build, as it does today.
Minor Changes
-
Lockfiles are no longer linted. xo now ignores
**/package-lock.jsonand
**/npm-shrinkwrap.jsonby default, so our lockfile layer, which only turned
offjson/no-empty-keysfor npm's root-package key, had nothing left to apply
to and has been removed. -
@typescript-eslint/no-floating-promisesallows un-awaitednode:test
calls.test(),it(),describe()and their siblings return promises
that the runner tracks itself, and xo now lists them as known-safe.
v28.0.0
Major Changes
-
@typescript-eslint/no-floating-promisesand
@typescript-eslint/restrict-template-expressionsare now on. Both had been
off since 2020, when our TypeScript rules were first added.eslint-config-xo
enables both, so this change deletes our two overrides rather than configuring
anything: the rules arrive with xo's options, not the plugin defaults.Measured across 296 TypeScript files in three consumers —
c4-sw-ac-patterns
(221),linkedin-talent-connect(66) andlighthouse-parade(9) — this is
nineno-floating-promisesreports and two
restrict-template-expressions. Neither rule is auto-fixable, though
no-floating-promisesoffers editor suggestions, so anything either one
catches has to be edited by hand. That is what makes this a major release.no-floating-promisesreports a promise that is started and then abandoned —
noawait, no.catch(), novoid. It runs with xo'signoreVoid, so
void doThing()marks one as deliberately unawaited, and xo relaxesno-void
to permit that form. The old reasoning for turning it off — that humans should
decide when to handle a rejection — is whatvoidexpresses; the difference is
that the decision is now visible at the call site instead of indistinguishable
from an oversight. Six of the nine reports in the sample were un-awaited
waitFor()calls in test files, where the assertion inside could not fail the
test.restrict-template-expressionsruns with xo'sallowNumber: trueand leaves
allowAnyat its default, so interpolating ananyinto a template literal is
still allowed — the specific allowance our override was written for survives
the rule being on. What it reports is values with no useful string form. Both
reports in the sample werenever, from setters whose type guard narrows the
guard's own failure branch.The
@typescript-eslint/no-unsafe-*family stays off, and so does
no-unsafe-type-assertion, which is a much larger migration (377 reports in
the same sample) and a separate question. -
func-namesis now on, at xo's setting ofnever. It reports a named
function expression —const handler = function onClick() {}— and wants
the name dropped, because since ES6 the variable name is inferred anyway. It
has nothing to say about function declarations.We had turned it off in the original 2018 config with no reason recorded
anywhere, so this adopts xo's position rather than reversing a decision of
ours. Zero reports across 434 files in three consumers — modern code reaches
for arrow functions, which the rule does not touch.The case it does cost you is naming a callback purely for debugging, such as
setTimeout(function tick() {}, 100). Inference does not apply to a function
passed straight as an argument, so that name is the only thing that shows up
in a stack trace or profiler. If you want it,// eslint-disable-next-line func-nameson that call is the escape hatch. -
capitalized-commentsnow extends xo's ignore pattern instead of replacing
it. The pattern exists so autofix does not capitalise commented-out code,
which then has to be un-capitalised when you uncomment it. Ours was forked
from xo's years ago and had drifted: we were missing the starts xo added since
(if (,for (,while (,switch (,class,import,export,
type-coverage:), and xo was missing three of ours (return,await,
console). Both sets now apply, and anything xo adds later arrives on its own.Almost entirely a widening, but not purely: xo matches
constwith a
trailing space where our old pattern matched a bareconst, so a prose comment
that merely starts with those letters —// constant— is now reported where
it was not. One such report across 434 files.
Minor Changes
-
@typescript-eslint/strict-boolean-expressionsis now off. It arrived in
v26.0.0 with the move toeslint-config-xo, which enables it with
allowStringandallowNumbertightened tofalse. It was the single largest
source of TypeScript-only reports in the config, and measurement did not
justify the cost: 165 reports across 294 TypeScript files in three
consumers, with no genuine defect in a 34-report sample spanning every
category the rule distinguishes. What it flagged was optional string
attributes,|| ''defaults and guard clauses — cases where'',0and
absent legitimately mean the same thing.Loosening it rather than removing it does not work, which is worth recording
because it is the obvious first idea. SettingallowString/allowNumberback
to the plugin defaults clears only 21% of the reports, because three quarters
of the remainder isstring | undefinedin a condition — rejected by xo and by
the rule's own defaults alike. Allowing enough to clear that leaves eight
reports, and none of those is a defect either.Upstream has declined to loosen the rule
(xojs/eslint-config-xo#108),
so this is ours to set rather than something to wait on. Projects that want it
can turn it back on in their own config.No project gains a report. Anyone who has already rewritten conditions to
satisfy it keeps working code; the rule simply stops asking. -
Twenty entries that restated a value
eslint-config-xoalready sets have
been removed. No rule changes severity or options as a result — every one was
verified to resolve identically with and without our line, and the rule
inventory is unchanged across the removal.What it changes is the future. A rule we no longer name is one we would follow
xo on if it changed its position, rather than silently pinning the old value.
That is deliberate: restating a value is a standing commitment to track every
option that rule later grows, and two of our entries had already drifted out of
step that way without anyone noticing.
v27.1.0
Minor Changes
-
@typescript-eslint/no-restricted-typesis now scoped to TypeScript files.
We had it configured in the layer that covers every code file, which quietly
made the@typescript-eslintplugin mandatory for linting anything at all. A
project with no TypeScript installed did not skip the rule, it failed outright:A configuration object specifies rule "@typescript-eslint/no-restricted-types", but could not find plugin "@typescript-eslint".No project gains or loses a report. The rule only ever matches type
annotations, so it could not fire on JavaScript in the first place — measured
against a real consumer project it changed nothing across 66 TypeScript files
and 120 single-file components, and the rule inventory records exactly one
difference, on the JavaScript probe. What changes is that a JavaScript-only
project can now lint withtypescriptabsent fromnode_modules.Note that npm may install TypeScript anyway.
eslint-config-xodepends on
typescript-eslint, which declares a requiredtypescriptpeer, so a
project with no TypeScript still gets roughly 31 MB of it. Nothing in this
config can prevent that; it needs a fix upstream, which we have reported as
xojs/eslint-config-xo#126.
v27.0.0
Major Changes
-
Update
eslint-config-xoto v2, which moveseslint-plugin-unicornfrom
v73 to v74. The bump adds, removes and reconfigures no unicorn rule — v74's
changes are fixes to rules we already run. Measured against a real consumer
project, it produced three fewer reports across 64 TypeScript files, where
unicorn/prefer-number-coercionandunicorn/prefer-string-replace-allhad
been flagging cases they misread. -
n/prefer-process-get-builtin-moduleis now on, added by xo v2. It reports
require('node:fs')andawait import('node:fs')for built-in modules and
wantsprocess.getBuiltinModule('node:fs')instead. It is not auto-fixable,
so whatever it catches has to be edited by hand. This is the only rule the
upgrade turns on, and the only source of new errors in it.Most projects will never see it. The rule switches itself off unless the
nearestpackage.jsondeclares anengines.nodefalling entirely within
^20.16.0 || >=22.3.0— the versions whereprocess.getBuiltinModuleis
guaranteed to exist. A package with noengines.nodeat all, or one as loose
as>=20, is left alone.Where it does apply, the payoff is in ES modules, where it removes the need
forcreateRequire. It fires in CommonJS too, whererequireis the native
idiom and the rewrite buys nothing, so build scripts and.cjsfiles are the
likeliest place to meet it. Turn it off for those paths in your own config if
it is noisy:{ files: ['**/*.cjs'], rules: { 'n/prefer-process-get-builtin-module': 'off' }, }
v26.1.0
Follow-up to v26, from rolling it out across our repos. Everything here either
turns a rule off or widens what one accepts, so no project should gain new
errors from upgrading.
Minor Changes
-
Scope xo's
package.jsonrules to the root manifest. They were applied to
every**/package.json, but they describe a package's manifest. Packages that
ship a dual CommonJS/ESM build also ship marker files whose entire content is
{"type": "commonjs"}or{"type": "module"}, and those collected demands for
name,version,license,keywordsand an entry point they will never
have — 13 reports across two two-line files in one of our packages. Worse,
prefer-type-moduletold a{"type": "commonjs"}marker to become
"module", and following that breaks the CommonJS half of the package.If you keep real manifests outside the repository root, such as npm
workspaces, widen the scope in your own config. See the README. -
Turn off
package-json/prefer-exports,package-json/prefer-type-module
andpackage-json/require-engines. Addingexportsor"type": "module"
changes what a published package exposes to people already consuming it: deep
imports start throwingERR_PACKAGE_PATH_NOT_EXPORTED, andrequire()stops
working. Both are worth doing, but as deliberate major releases rather than as
lint errors, and both are meaningless for applications that never publish.
engines.nodeis fair for packages that run in Node, but we publish browser
libraries with no Node runtime to describe, and inventing a range to satisfy
the rule makes the field less trustworthy where it matters.package-json/prefer-files-fieldstays on. It skips private packages already,
so it only speaks up for genuinely publishable ones — and it caught a real bug
for us, where a published tarball contained the entire repository.The README now lists these three as recommended settings to adopt in packages
where they apply. -
Turn off
@html-eslint/require-open-graph-protocoland
@html-eslint/require-meta-description. OGP requiresog:title,og:type,
og:urlandog:imageon every HTML file in a project. Demo pages, test
fixtures, component examples and email templates all match, and none of them
are ever shared as a link. Which pages get Open Graph tags, and what those tags
say, is a per-page content decision. Neither rule is in html-eslint's own
recommended set; xo adds them. The rest of xo's HTML rules are kept, including
require-lang,require-meta-charsetandrequire-meta-viewport. -
Turn off
unicorn/no-non-function-verb-prefix. It requires anything named
with a verb prefix to be a function, so a variable holding a callback gets
reported as`setFocus` starts with `set`, so it should be a function.
Same family of naming opinion asunicorn/consistent-boolean-nameand
unicorn/name-replacements, which we already disable for the same reason. -
Allow indented continuation lines in JSDoc blocks.
jsdoc/check-indentation
andjsdoc/check-line-alignmentbetween them forbade indenting the wrapped
lines of a@paramdescription, and--fixsilently flattened existing
indentation:* @param {string} opts.waitMode - Determine how the library should check that - * hiding transitions are complete. The options are `'transitionEnd'`, + * hiding transitions are complete. The options are `'transitionEnd'`,A tag's description runs until the next tag and leading whitespace is stripped
before parsing, so the indentation is purely cosmetic — it just makes it easier
to see where one@paramends and the next begins. Both styles are now
accepted. An indented tag line is still reported, since that is a real
mistake.
Notes for anyone upgrading from v25
These describe v26 behaviour rather than anything new in this release, but they
are the two things that caught us out, so they are worth writing down.
-
@html-eslint/require-button-typeautofixes totype="submit". The rule
is a good one and stays on, but its fixer insertssubmitunconditionally,
with no regard for whether the button is in a form.submitis the HTML
default, so nothing changes behaviourally and nothing looks wrong in review —
but for a JavaScript-driven button the markup then says the opposite of what
is true. Check the result on any page with buttons:- <button class="js-toggle">Show</button> + <button type="button" class="js-toggle">Show</button>
-
Rule overrides need a
filesglob. xo scopes its layers to the file types
they lint and registers plugins on those layers, so arulesblock with no
filesnow also applies to JSON, Markdown, CSS and HTML, where those plugins
were never registered. ESLint then refuses to load the config at all:A configuration object specifies rule "jsdoc/check-indentation", but could not find plugin "jsdoc".{ + files: ['**/*.{js,cjs,mjs,ts}'], rules: { 'jsdoc/check-indentation': 'off', }, }This is easy to misread as a dependency problem, because it passes when you
lint a single file and only fails oneslint ..
v26.0.0
Breaking Changes
-
Rebuild on
eslint-config-xov1 and remove the vendored copy ofeslint-config-standard. xo now bundles most of what we assembled by hand, soeslint-config-prettier,eslint-plugin-import-x,eslint-plugin-jsdoc,eslint-plugin-n,eslint-plugin-unicornandtypescript-eslintare no longer direct dependencies — they arrive through xo, and xo decides their versions. Three direct dependencies remain:eslint-config-xo,eslint-plugin-promiseandglobals. Our own overrides are unchanged.Of the 106 rules the vendored standard config still contributed, xo already enabled 95. The seven that survive (
no-empty-character-class,no-invalid-regexp,no-useless-backreference,n/handle-callback-err,n/no-callback-literal,n/no-exports-assign,promise/param-names) are now set explicitly.standard's remaining value was formatting, which Prettier already handles for us.This turns on a meaningful number of new rules. Measured against a real project, roughly 250 new reports across 64 TypeScript files, mostly from
require-unicode-regexp(thevflag),@typescript-eslint/strict-boolean-expressions,curlyandn/prefer-global/process.xo also lints
package.json, JSON, Markdown and CSS, which it did not before. One of itspackage.jsonrules is off:dependency-version-range, because we pin exact versions deliberately and it fires on every project, including applications that never publish anything. The rest apply, includingsort-properties, which reorders top-level fields to npm's canonical sequence the first time it runs.Three of xo's opinions are turned back off because they contradict decisions we had already made:
@typescript-eslint/no-restricted-typesno longer bansnullas a type. That is the same opinion asunicorn/no-null, which we have always disabled. xo's other restrictions are kept.@typescript-eslint/naming-conventionis off. It cannot distinguish our names from names that come from someone else's contract, so it flags things likeAuthorizationheaders and generated GraphQL types.jsdoc/require-asterisk-prefixis back toalways. xo sets it tonever, which wants JSDoc written without the leading asterisk on continuation lines.
-
Import rules move from the
import/*namespace toimport-x/*. We used to depend oneslint-plugin-importand later replaced it witheslint-plugin-import-x, registered under the nameimportto keep rule names stable.eslint-plugin-importhas not released since June 2025 and does not support ESLint 10, which is what forced the swap. Now that xo bundles import-x under its ownimport-xnamespace, we use that directly. Anyeslint-disablecomment naming an import rule needs updating:- // eslint-disable-next-line import/order + // eslint-disable-next-line import-x/order
-
Replace
mainwithexports. The package now declares"exports": "./eslint.config.js"instead of"main", and adds"sideEffects": false.import cloudFourConfig from '@cloudfour/eslint-config'is unaffected. What changes is that deep imports are now blocked:import '@cloudfour/eslint-config/eslint.config.js'throwsERR_PACKAGE_PATH_NOT_EXPORTED. That path resolved to the same module as the package root, so it was an unusual thing to write, but anything doing it needs to import the package root instead. -
Drop ESLint 9 support. The
eslintpeer range is now^10.6.0. The previous>= 9range was never accurate either —eslint-plugin-unicornhas required>=9.38.0since v63. -
Remove the custom
@cloudfour/prefer-early-returnrule, along with the@cloudfourplugin namespace.eslint-plugin-unicornships an equivalentunicorn/prefer-early-return, which we now use withmaximumStatements: 2so it reports exactly the same cases the old rule did. We would rather not maintain a lint rule that exists upstream.Any
eslint-disablecomments naming the old rule will now fail with "Definition for rule '@cloudfour/prefer-early-return' was not found". Replace them:- // eslint-disable-next-line @cloudfour/prefer-early-return + // eslint-disable-next-line unicorn/prefer-early-return
-
Change the supported Node versions from
>=22.16.0to^22.13.0 || >=24, matching what ESLint and our other dependencies actually support. This drops Node23.x, which was never really supported.
Major Changes
-
Require ESLint
v10.6or newer.eslint-config-xodeclares>=10.4, but it configuresno-constant-binary-expressionwith an option ESLint did not support untilv10.6, so its own range is optimistic. Our CI job that runs the suite against the oldest ESLint we advertise is what caught this. -
ecmaVersionis no longer pinned to2022; xo'slatestapplies. Pinning it lower maderequire-unicode-regexpunsatisfiable, since thevflag it requires is newer than the syntax we allowed. -
eslint-plugin-unicornmoves fromv63tov73(via xo), which enables around 178 additional rules. Some existingeslint-disablecomments for unicorn rules may become stale and need removing.Renamed upstream, so any
eslint-disablecomments using the old names need updating:unicorn/prevent-abbreviations→unicorn/name-replacements(still off)unicorn/no-array-for-each→unicorn/no-for-eachunicorn/prefer-dom-node-dataset→unicorn/dom-node-datasetunicorn/no-hex-escapewas removed, replaced byunicorn/prefer-unicode-code-point-escapes
Newly disabled, because they conflict with conventions or duplicate a rule we already run:
unicorn/consistent-boolean-name— requires booleans to be namedisFoo/hasFoo/etc. A naming opinion, left to reviewers, consistent with our stance onunicorn/name-replacements.unicorn/single-line-block-comment-style— would expand single-line/** @type {…} */JSDoc across multiple lines.unicorn/require-array-sort-compare— can't distinguish string arrays from number arrays without type information. Replaced with the type-aware@typescript-eslint/require-array-sort-comparefor TypeScript files.
Reconfigured:
unicorn/prefer-early-returnreplaces our removed custom rule, set tomaximumStatements: 2to match it. See the breaking change above.unicorn/filename-casenow checks directory names as well as filenames. We ignore__tests__-style directories so conventional names like__tests__,__mocks__and__snapshots__don't have to be renamed.@typescript-eslint/no-unnecessary-boolean-literal-compareis now off in favour ofunicorn/no-unnecessary-boolean-comparison, which covers the same cases without requiringstrictNullChecksand also applies to JavaScript.
-
eslint-plugin-nmoves fromv17tov18(via xo). -
eslint-plugin-jsdocmoves fromv62tov63(via xo). An earlier commit in this release took it tov64directly; xo pins^63.3.3, sov63is what ships.
Minor Changes
- Only publish
eslint.config.jsto npm. The package previously also shipped repository files such as.editorconfig,.nvmrc,.renovaterc.jsonand the CI workflow.
v25.0.1
Minor Changes
- Disable a few stylistic rules that aren't currently covered by
eslint-cofig-prettier.
v25.0.0
Breaking Changes
- Update minimum Node version to
v22
Major Changes
- Update
eslint-config-prettiertov10 - Update
eslint-plugin-jsdoctov62 - Update
eslint-plugin-unicorntov63
v24.0.0
Breaking Changes
- Changed from a "super plugin" to a flat config file
- Changed package name from
@cloudfour/eslint-pluginto@cloudfour/eslint-config
Major Changes
- Update
eslint-plugin-jsdocfromv46tov50 - Update
eslint-plugin-nfromv16tov17 - Update
eslint-plugin-promisefromv6tov7 - Update
eslint-plugin-unicornfromv48tov56 - Update
typescript-eslintfromv6tov8
v23.0.0
Major Changes
-
#551
f11e794Thanks @calebeby! - Improve linting oftypemodifiers on imports.Reconfigured Rules (
recommendedConfig)@cloudfour/import/no-duplicates- "error" + [ + "error", + { + "prefer-inline": true + } + ]
Newly Enabled Rules (
recommendedConfig » TS Overrides)