Minor Changes
-
#520
802441cThanks @SwastikTripathi! - Add acheckSelfReferenceoption to theextensionsrule. When set, a package's import of its own subpath export (e.g.pkg/subfrom insidepkg) is treated as a package import, soignorePackagesapplies to it. -
#403
f4d0a4bThanks @Zamiell! -extensions: when an import has no extension and cannot be resolved as-is, try the extensions configured asalwaysinpatternand use the unique one that resolves for the fix or suggestion. If several resolve, report the missing extension without guessing. When no extension can be determined automatically, explain that in themissingmessage instead of only reporting a bare missing extension. -
#572
fc17206Thanks @SwastikTripathi! - feat(no-duplicates): add awhitelistoption listing entry points that share one declaration file but are different modules, such assvelteandsvelte/easing -
#504
f10dcc4Thanks @morgan-coded! - Add a"top"mode to theorderrule'swarnOnUnassignedImportsoption for requiring no-specifier ESM imports before assigned imports. -
#502
9c85cedThanks @SukkaW! - Rewrite the module analysis core for performance boosts. The speedup depends on your project, and we expect no performance regression in any setup — if you measure one, please report it.Rules like
named,default,namespace,no-cycle,no-deprecated, and many more, need to know what the modules you import actually export. Until now, answering that meant running the full ESLint parser over every one of those files — including everything you pull in fromnode_modules.That core has been rewritten:
- Plain JavaScript is now read with
es-module-lexerinstead of being parsed. Anything it can't read faithfully — TypeScript, JSX, Flow — still goes through the ESLint parser exactly as before. - Analysis is lazy. Much of the work now happens when a rule actually asks for it, so enabling one rule no longer pays for what the others would have needed.
- Results are cached and shared between rules.
There are no breaking changes: no configuration changes are needed, and no behavior changes are intended. Every rule should report exactly what it reported before, with one known exception: an imported plain JavaScript file that has a syntax error no longer gets a "Parse errors in imported module" report. ESLint still reports the syntax error when it lints that file itself. The full test suite passes, but there could be some edge cases. If a rule starts reporting something it didn't before, or stops reporting something it should, please open an issue with a minimum reproduction — bug reports on this are very welcome.
- Plain JavaScript is now read with
-
#456
7b78aaeThanks @43081j! - Add four barrel file detection rules ported fromeslint-plugin-barrel-files:avoid-barrel-files,avoid-importing-barrel-files,avoid-namespace-importandavoid-re-export-all.avoid-importing-barrel-fileschecks every runtime way a module can be referenced — static imports, re-exports (export … from,export * from,export * as ns from), dynamicimport()and CommonJSrequire()— with type-only statements exempt. -
#157
89cfe42Thanks @Zamiell! - feat(no-rename-default): accept names derived from bare package paths and add awhitelistoption for modules that mangle their default export names
Patch Changes
-
#562
7cbe683Thanks @JounQin! - perf(avoid-importing-barrel-files): skip the surface parse for a dependency that cannot be a barrelThe rule sizes the runtime module graph behind every imported target, and a
target only becomes a candidate once its export count passes
amountOfExportsToConsiderModuleAsBarrel. A lexer-analyzed module already
reports its export entries, so a count at or below the threshold settles the
question without producing the exact export/declaration surface — one parse
saved per small plain-JS dependency.Anything es-module-lexer does not read faithfully still goes through the parser,
and a module whose source declares a default export is never ruled out: the
properties ofexport default { … }are exports no entry count can see (a
defaultthatesModuleInteropsynthesizes does not count).A declarator that binds no name —
export const {} = obj,export const { a: {} } = obj— is no longer counted as an export by the barrel surface: it
exports nothing, and counting it made the surface disagree with the lexer's
entry count. -
#568
225a5aaThanks @SwastikTripathi! - Fixno-extraneous-dependenciestelling you to add a package that is already indevDependenciesoroptionalDependencieswhen its import resolves through@types/* -
#579
db16385Thanks @thekhegay! - fix(core): keep the size ofsettingsout of module and resolver cache keysEvery key held the full
stableHashtext ofsettings,parserOptionsand the parser. One large value in sharedsettings, like a custom alphabet fromeslint-plugin-perfectionist, was copied into each key on every resolve, sono-cycleand any other rule that resolves imports could run out of memory. Keys now hold a short id per distinct hash. -
#309
4b9ea02Thanks @remcohaszing! - Allow packages to import themselves inimport-x/no-extraneous-dependencieswhen theexportsfield is defined inpackage.json; importing a package's own name withoutexportsis reported by the newselfImportmessage. -
#565
3d0f5a9Thanks @SwastikTripathi! - fix(namespace, no-deprecated): don't report members of a parameter that shadows a namespace import when the namespace is also used in the same scope, e.g. in a type annotation -
#539
bb7b18bThanks @SwastikTripathi! - chore(deps): upgradees-module-lexerfrom v2 to v3. The v3 API is a complete redesign using discriminated union types (StaticImport | DynamicImport | ImportMetaReffor imports,DirectExport | Reexport | ReexportAllfor exports) replacing numericdfields and short property names. The lexer route now resolvesexport { x }wherexcomes from a non-namespace import as a reexport rather than an own export, which is more semantically accurate. -
#566
4c66c87Thanks @SwastikTripathi! - fix(export): don't report a namespace that only declares types as a duplicate of a value with the same name -
#536
db674b5Thanks @SukkaW! - fix(export): check types and values separatelyexport type * fromonly re-exports types, so it no longer conflicts with a value export of the same name, and a type reached throughexport *no longer conflicts with a value. Duplicate types among re-exports are now reported, as are a type-only export and a value export of the same name, and ambient function overloads that clash with another export. String-literal export names such as"type:Foo"are no longer mistaken for type exports. Fixes #414. -
#594
0c82c7eThanks @i1stG! - fix(extensions): resolve directory imports to their index file in autofixes and suggestions, and only offer rewrites of resolved imports that point back to the same file. -
#571
2bc321dThanks @TonyTech404! - Fix theextensionsrule treating an extension alias as a missing extension. An import such as./foo.jsthat the resolver maps tofoo.tsnow keeps its own extension instead of being reported as missingtsand autofixed to./foo.js.ts. Thepatternextension inference also recognizes an extension alias, so an extensionless./foothat resolves that way is fixed to./foo.js. The inference only accepts a candidate that resolves to the same file, and an ambiguous result is reported without a fix. Type-only imports and exports (including inlinetypespecifiers) are ignored before the extension branches, so an aliased.jsspecifier is preserved whencheckTypeImportsis off. -
#512
42dba04Thanks @LeulTew! - fix(extensions): honorpathGroupOverrideswhen nopattern,ignorePackages, orcheckTypeImportsoption is provided. -
#575
27112d6Thanks @SwastikTripathi! - fix: keep trailing comments and line-scoped directives with their targets.firstinserts moved imports right after the directive/statement, before any trailing comment, and withholds the autofix for the whole group when a moved import has comments.newline-after-importinserts the blank before a single-line trailing comment (or after a multi-line one), so third-party directives such asprettier-ignorekeep their target. Both rules share thegetTrailingCommentsutil. -
#555
27876f2Thanks @JounQin! - perf(core): memoizegetExportKindper module and nameRules ask for an export kind once per imported specifier and once per
namespace member access. Without a memo, every lookup re-walks allexport *
paths and re-resolves each target; on a 100-star barrel, 2,000 namespace
member accesses took ~5.7 s instead of ~0.1 s. Answers are cached per
(module, name, typeOnly), so the type-only and value answers stay
independent. -
#519
f6a66a0Thanks @Cherry! - perf: cache legacy resolver lookups per directory instead of re-resolving the resolver package on every import resolution. -
#570
7254048Thanks @devon-scythe! - fix: support minimatch v9/v10/v11 and drop makeReminimatch v11 is the only release line with the fix for GHSA-g3hv-75wg-m249.
It removesmakeRe(), sono-internal-modulesnow matches itsallowand
forbidglobs withMinimatch#match. -
#535
66632ecThanks @SwastikTripathi! - fix(core): send a.jsfile to the AST route when es-module-lexer cannot read one of its exports. On the lexer fast path, an export the lexer does not read was dropped from the module's export set as soon as the file had any export it did read, sohasExport/getExportNames— andnamed,namespace,default,no-named-as-defaultwith them — reported the name as missing. The lexer route now keeps only the declaration heads whose exported names are read faithfully by es-module-lexer v3 (default,{,*,function,class,const,let,var) and bails on every unrecognised head as a conservative fallback, includingmodule,using,await using,@dec class,abstract class,declare,enum,const enum,namespace, andinterface(v3 reads those, but they are not yet in the whitelist). -
#534
595f2d7Thanks @SwastikTripathi! - fix(module-visitor): treatimport x = require('y')as a module reference, so every rule built onmoduleVisitor(no-unresolved,no-cycle,extensions,no-extraneous-dependencies,no-restricted-paths,no-absolute-path, …) sees the specifier.export import x = require('y')was missed too, because itsExportNamedDeclarationhas anullsource.no-cycleandavoid-importing-barrel-fileskeep ignoring the type-only form,import type x = require('y') -
#556
19ff6c5Thanks @SwastikTripathi! - fix(namespace): report member access in decorator expressions by walking up to the enclosing module scope. -
#567
b2afa70Thanks @SwastikTripathi! - fix(namespace): don't report a heritage clause that names a type from a module resolved to JavaScript, which has no types to check against. -
#526
8f4f6b9Thanks @JounQin! - fix: countexport * as ns from '…'as a runtime dependency in the module analysis coreexport * as ns from 'x'loadsxat runtime, but bothModuleInfoconstruction paths omitted the edge, so graph traversal — andavoid-importing-barrel-filesin particular — could undercount the modules an import pulls in. The namespace export metadata is unchanged, andexport type * as ns fromstays type-only. Fixes #525. -
#541
bbd0026Thanks @MFA-G! - fix(namespace): report members exported only as typesA name exported only as a type (
export type Foo,export interface Foo,export type { Foo },export type * from) is erased at runtime, so accessing it as a member of an imported namespace (ns.Foo,const { Foo } = ns,<ns.Foo />) is now reported. Classes, enums and names that are both a type and a value are still accepted, and unresolvable names still fail open. Fixes #540. -
#543
35fc9bfThanks @thekhegay! - fix(newline-after-import): ignore comments between two imports whenconsiderCommentsis enabled, matchingeslint-plugin-import. -
#577
5169813Thanks @SwastikTripathi! - fix(newline-after-import): treat a comment on the import's own line as part of the importWith
considerComments, a trailing comment such asimport a from 'a' // why
was taken as the comment after the import, so the rule reported even with an
empty line before the next statement. The autofix also inserted the new lines
between the import and its trailing comment, moving the comment onto its own
line. Both now skip comments that start on the statement's last line.The fix is withheld when any trailing comment is a line-scoped directive
(eslint-disable-next-line,@ts-ignore,@ts-expect-error), so the directive
keeps its target, and comments inside the following statement no longer satisfy
the blank-line check. -
#591
65ea15cThanks @SwastikTripathi! - fix(no-absolute-path): prefix./when the fixed path starts with a dot directory such as.config/, which would otherwise turn into a package import -
#507
aa1071dThanks @BridgeAR! - Reduceno-cycleCPU usage for converging dependency graphs. -
#518
c45cfb9Thanks @wimvanschandevijl! - fix(no-cycle):allowUnsafeDynamicCyclicDependencyno longer hides static-only cycles that happen to sit in the same module as an unrelated dynamic importPreviously,
no-cycle's traversal bailed out of a module's entire remaining import list as soon as it found one path where any declaration was dynamic -returnrather thancontinueinside the loop overm.imports. That meant:- a purely static cycle declared after an unrelated dynamic import earlier in the same file went unreported, and
- a path imported both statically and dynamically (the static declaration on its own closing a real cycle) was skipped entirely, because "any declaration to this path is dynamic" discarded the static declaration too.
Both are now handled per-declaration: only the dynamic declarations for a path are dropped when
allowUnsafeDynamicCyclicDependencyis set, and only that one path is skipped when nothing traversable remains - the module's other, unrelated import paths (and any static declaration to the very same path) are still checked. See #515 for the repro that found this. -
#538
b6a674bThanks @b84330808! - fix(no-cycle): report cycles through side-effect imports such asimport './foo'andimport {} from './foo'. They have no specifiers, so they were skipped as if they were type-only imports. The unambiguous-module prefilter also recognizes them now, so a file whose only module syntax is a side-effect import is analyzed on the AST route too, as it already was on the lexer route -
#585
8777312Thanks @thekhegay! - fix(no-duplicates): do not treat commas inside comments as specifier separators in the autofix -
#581
76e97dbThanks @thekhegay! - fix(no-duplicates): imports of one path with different attributes are not duplicates, and the autofix no longer drops an attribute, mistakes the braces ofwith {...}for the braces of the specifiers, or takes aresolution-modeimport out ofimport type -
#584
85c1b88Thanks @thekhegay! - fix(no-duplicates): remove extra whitespace before the closing brace when merging import specifiers -
#522
56b1c48Thanks @giaBaoJS! - fix(no-duplicates): the autofix no longer dropsxwhen mergingimport { x }into an import ofx as y, andprefer-inlineno longer turns thefromkeyword or an alias intotype ...when the first import isimport type { ... } -
#546
1eb95b0Thanks @thekhegay! - fix(no-duplicates): report a module imported twice withimport type * as, as it already does forimport * asandimport type x -
#548
d565b2fThanks @thekhegay! - fix(no-duplicates): withoutprefer-inline, the autofix moves inlinetypespecifiers into theimport type { ... }of the same module and leaves the value specifiers in their own import, instead of merging both into one import that is invalid or changes which specifiers are type-only. -
#574
2fd859dThanks @SwastikTripathi! - fix(no-empty-named-blocks): use the reported import's tokens in the side-effect suggestionThe suggestion looked up
importandfromin the whole file, so an earlier import with afromclause, likeimport a from 'a', supplied the tokens and the suggestion left the code unchanged. Earlier side-effect imports have nofromand weren't affected. -
#547
3598430Thanks @thekhegay! - fix(no-extraneous-dependencies): report the runtime package instead of its@types/*package when an import resolves to type definitions, matchingeslint-plugin-import. A@types/*package independenciesno longer counts as a declaration of its runtime package, andwhitelistaccepts either name. A type-only import checked withincludeTypesstill needs only the@types/*package. -
#571
2bc321dThanks @TonyTech404! - fix(no-extraneous-dependencies): ignore inline type-only export specifiers whenverifyTypeImportsis off, as inline type imports already are -
#580
059ca92Thanks @thekhegay! - fix: ignore type-only named exports forno-named-as-defaultandno-named-as-default-member -
#573
a3553f3Thanks @SwastikTripathi! - fix(no-namespace): don't autofix computed member access on the namespacefoo[key]was fixed as if it werefoo.key, importing an export namedkeyand dropping the lookup,list[foo]was rewritten the same way, andfoo[1]producedimport { 1 }, which doesn't parse. All of them keep the namespace import now, and the rule still reports them. -
#561
981ada8Thanks @wleeaf! - fix(no-namespace): check all enclosing scopes for name conflicts in the autofixThe autofix only looked at the innermost scope of each
ns.namereference and its parent for names that would shadow the new import, so a variable declared further out (or an undeclared global used elsewhere in the module) changed what the rewritten reference pointed to. It also turnedns.defaultandns['some-key']into named imports that aren't valid syntax; those are now reported without a fix. -
#582
47c5419Thanks @thekhegay! - fix(no-namespace): don't crash or write broken code on members named likeObject.prototypeones, e.g._.toString -
#583
2538093Thanks @thekhegay! - fix: don't crash withThe "path" argument must be of type stringwhen there is nopackage.jsonabove the linted file. The directory ESLint runs in stands in for the root of the package, andno-extraneous-dependencieswithoutpackageDirlints nothing, as its documentation says -
#560
4afef66Thanks @wleeaf! - fix(no-relative-packages): keep the imported path when suggesting the package importThe suggested package import was built from the directory of the resolved file plus the last segment of the import path. An extensionless import of a file named like its package directory (
../bar/src/bar) becamebar/src, which resolves to another module, and a directory import (../bar/src) becamebar/src/src. The suffix is now taken from the resolved file below the package root: explicit import extensions are preserved, extensionless imports omit the resolved extension, directory index files collapse back to the directory, and.d.tstargets drop the.dmarker. If the package name itself resolves to the original module, it is preferred (for example../node_modules/nested-mainbecomesnested-main). The autofix is only offered when the suggested package path resolves back to the original module; otherwise the rule reports without suggesting that path. Packages reached through symlinks (pnpm, workspaces) therefore keep their package name and path. -
#524
2384a48Thanks @giaBaoJS! - fix(no-unused-modules):missingExportsno longer reports "No exports found" for a file whose only exports areexport * fromdeclarations, andunusedExportsnow checksexport * as ns fromas the exportnsand counts its source module as namespace imported -
#533
6ffa22eThanks @giaBaoJS! - fix(no-unused-modules): exports namedImportDefaultSpecifier,ImportNamespaceSpecifierorExportAllDeclarationno longer collide with the rule's internal bookkeeping keys, andimport { default as x }now counts as a use of the default export -
#593
9144c94Thanks @thekhegay! - fix(order): move all out of order imports of a block in one fix, so one--fixis enough for a block with more than 10 of them -
#544
1a2bf7aThanks @thekhegay! - fix(order): compare statement indexes as numbers, so the autofix no longer moves an import across a non-import statement when the indexes sort differently as strings, such as 2 and 10. -
#517
d6808bdThanks @Cherry! - fix(order): make the alphabetize comparator transitive, so parent imports now consistently sort before sibling imports in the same group (matchingeslint-plugin-import) and results no longer depend on the Node version -
#578
57729aeThanks @SwastikTripathi! - fix(prefer-namespace-import): keep a namespace specifier when splitting the default importThe autofix looked for
{and}to find the other specifiers, so
import Foo, * as Bar from 'foo'was rewritten into code that does not parse.
It now takes the text between the comma after the default specifier andfrom,
which also covers* as Bar. -
#564
ba0b9f6Thanks @SwastikTripathi! - fix(core):declare namespace foo.bar.baz {}behindexport = foonow only addsbarto the module's exports.bazand the block's members were previously reported as direct exports, sonamedacceptedimport { baz }andimport { deep }, and a qualifieddeclare module foo.qux {}exposed its members too. -
#482
53a88f3Thanks @B4nan! - Merge dependencies from the closestpackage.jsonin addition topackageDirinno-extraneous-dependencies, so workspace packages see the dependencies declared in their own manifest;bundledDependenciesfrom every merged manifest are unioned instead of overwritten. -
#492
9669829Thanks @andersk! - Ignore TypeScriptnamespacemembers forno-named-as-default,no-named-as-default-memberrules -
#590
8c11eb7Thanks @SwastikTripathi! - fix(consistent-type-specifier-style): keep the quotes of a string specifier name, liketype "a-b" as AB, when theprefer-top-levelfix moves it into a newimport type
New Contributors
- @andreww2012 made their first contribution in #410
- @LeulTew made their first contribution in #512
- @wimvanschandevijl made their first contribution in #518
- @AdamMagued made their first contribution in #521
- @giaBaoJS made their first contribution in #522
- @Cherry made their first contribution in #517
- @SwastikTripathi made their first contribution in #520
- @andersk made their first contribution in #492
- @dead-claudia made their first contribution in #506
- @BridgeAR made their first contribution in #507
- @b84330808 made their first contribution in #538
- @thekhegay made their first contribution in #544
- @MFA-G made their first contribution in #541
- @remcohaszing made their first contribution in #309
- @wleeaf made their first contribution in #561
- @TonyTech404 made their first contribution in #571
- @devon-scythe made their first contribution in #570
- @i1stG made their first contribution in #594
Full Changelog: v4.17.1...v4.18.0