eslint-import-resolver-node's resolution does not match node's with respect to symlinks
Opened this issue · 1 comments
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.
jestjs/jest#5356 (comment) has some more of the context/history as well.
You are correct that this value should be set to false
by default. The reason that we don't do that in the node resolver at the moment is because that would be a breaking change.