Description
Consider this example project, structured similarly to what pnpm produces: t.tar.gz
The directory structure looks something like this:
t/
t/index.js
t/node_modules/
t/node_modules/b -> .pnpm/b@1.0.0/node_modules/b
t/node_modules/.pnpm/
t/node_modules/.pnpm/b@1.0.0/
t/node_modules/.pnpm/b@1.0.0/node_modules/
t/node_modules/.pnpm/b@1.0.0/node_modules/b/
t/node_modules/.pnpm/b@1.0.0/node_modules/b/index.js
t/node_modules/.pnpm/b@1.0.0/node_modules/b/package.json
t/node_modules/.pnpm/b@1.0.0/node_modules/c -> ../../c@1.0.0/node_modules/c
t/node_modules/.pnpm/c@1.0.0/
t/node_modules/.pnpm/c@1.0.0/node_modules/
t/node_modules/.pnpm/c@1.0.0/node_modules/c/
t/node_modules/.pnpm/c@1.0.0/node_modules/c/index.js
t/node_modules/.pnpm/c@1.0.0/node_modules/c/package.json
t/package.json
Of particular note, package b
depends on c
, and pnpm structures things as above so that t/index.js
won't see c
as a phantom dependency. c
exports a named export 'foo', and b
rexports that with export * from 'c'
.
When t/index.js
imports b
, standard Node module resoluton resolves that via the symlink t/node_modules/b
, returning the absolute realpath /some/absolute/path/t/node_modules/.pnpm/b@1.0.0/node_modules/b/index.js
. Resolving c
relative to that path finds it via the symlink t/node_modules/.pnpm/b@1.0.0/node_modules/c
.
eslint-import-resolver-node
's resolution differs in one key respect: by default, instead of returning the absolute realpath /some/absolute/path/t/node_modules/.pnpm/b@1.0.0/node_modules/b/index.js
, it returns /some/absolute/path/t/node_modules/b/index.js
instead. It then (correctly) cannot resolve c
relative to that path.
A relevant eslint.config.js
to use with the above could look like this:
import importPlugin from 'eslint-plugin-import';
export default [ importPlugin.flatConfigs.recommended ];
which will produce the following false positive due to this bug
1:10 error foo not found in 'b' import/named
If you want a "real" reproduction, starting in an empty project:
- Create a package.json with
"type": "module"
(or use extension.mjs
instead of.js
in steps 3 and 4). - Run
pnpm add eslint eslint-plugin-import @playwright/test
. - Create the above
eslint.config.js
. - Create a JS file containing
import { expect } from '@playwright/test';
. - Run eslint on that file.
As for a fix, it looks like setting preserveSymlinks: false
in
eslint-plugin-import/resolvers/node/index.js
Lines 11 to 20 in d5f2950
{
settings: {
'import/resolver': {
node: {
preserveSymlinks: false,
},
}
}
}
I note the README for the resolve package you use recommends setting preserveSymlinks: false
to match Node's behavior; as far as I can tell, preserving symlinks was only the default in Node for a short time between 6.0.0 and 6.2.0, although the documentation as to the expected behavior was unclear before that.