Skip to content

Commit 8ca9272

Browse files
JounQinscytackiautofix-ci[bot]arcanis
authored
fix: pnp issue when used in a monorepo (#390)
Co-authored-by: Scott Cytacki <scytacki@concord.org> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Maël Nison <nison.mael@gmail.com>
1 parent 14cb948 commit 8ca9272

File tree

12 files changed

+2165
-418
lines changed

12 files changed

+2165
-418
lines changed

.changeset/poor-dolls-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
fix: pnp issue when used in a monorepo

src/utils/module-require.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Module from 'node:module'
1+
import Module, { createRequire } from 'node:module'
22
import path from 'node:path'
33

44
import { cjsRequire } from '../require.js'
@@ -11,7 +11,7 @@ function createModule(filename: string) {
1111
return mod
1212
}
1313

14-
export function moduleRequire<T>(p: string): T {
14+
export function moduleRequire<T>(p: string, sourceFile: string): T {
1515
try {
1616
// attempt to get espree relative to eslint
1717
const eslintPath = cjsRequire.resolve('eslint')
@@ -32,6 +32,13 @@ export function moduleRequire<T>(p: string): T {
3232
//
3333
}
3434

35+
try {
36+
// try relative to the current context
37+
return createRequire(sourceFile)(p)
38+
} catch {
39+
//
40+
}
41+
3542
// finally, try from here
3643
return cjsRequire(p)
3744
}

src/utils/parse.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ export function parse(
117117
// require the parser relative to the main module (i.e., ESLint)
118118
const parser =
119119
typeof parserOrPath === 'string'
120-
? moduleRequire<TSESLint.Parser.ParserModule>(parserOrPath)
120+
? moduleRequire<TSESLint.Parser.ParserModule>(
121+
parserOrPath,
122+
context.physicalFilename,
123+
)
121124
: parserOrPath
122125

123126
// replicate bom strip and hashbang transform of ESLint

src/utils/resolve.ts

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export function fileExistsWithCaseSync(
4949
filepath: string | null,
5050
cacheSettings: NormalizedCacheSettings,
5151
strict?: boolean,
52+
leaf: boolean = true,
5253
): boolean {
5354
// don't care if the FS is case-sensitive
5455
if (CASE_SENSITIVE_FS) {
@@ -76,8 +77,12 @@ export function fileExistsWithCaseSync(
7677
} else {
7778
const filenames = fs.readdirSync(dir)
7879
result = filenames.includes(parsedPath.base)
79-
? fileExistsWithCaseSync(dir, cacheSettings, strict)
80-
: false
80+
? fileExistsWithCaseSync(dir, cacheSettings, strict, false)
81+
: !leaf &&
82+
// We tolerate case-insensitive matches if there are no case-insensitive matches.
83+
// It'll fail anyway on the leaf node if the file truly doesn't exist (if it doesn't
84+
// fail it's that we're probably working with a virtual in-memory filesystem).
85+
!filenames.some(p => p.toLowerCase() === parsedPath.base.toLowerCase())
8186
}
8287
fileExistsCache.set(filepath, result)
8388
return result
@@ -298,52 +303,61 @@ function fullResolve(
298303
node: settings['import-x/resolve'],
299304
} // backward compatibility
300305

301-
for (const { enable, name, options, resolver } of normalizeConfigResolvers(
302-
configResolvers,
303-
sourceFile,
304-
)) {
305-
if (!enable) {
306-
continue
307-
}
306+
const sourceFiles =
307+
context.physicalFilename === sourceFile
308+
? [sourceFile]
309+
: [context.physicalFilename, sourceFile]
308310

309-
// if the resolver is `eslint-import-resolver-node`, we use the new `node` resolver first
310-
// and try `eslint-import-resolver-node` as fallback instead
311-
if (LEGACY_NODE_RESOLVERS.has(name)) {
312-
const resolverOptions = (options || {}) as NodeResolverOptions
313-
const resolved = legacyNodeResolve(
314-
resolverOptions,
315-
// TODO: enable the following in the next major
316-
// {
317-
// ...resolverOptions,
318-
// extensions:
319-
// resolverOptions.extensions || settings['import-x/extensions'],
320-
// },
321-
context,
322-
modulePath,
323-
sourceFile,
324-
)
311+
for (const sourceFile of sourceFiles) {
312+
for (const {
313+
enable,
314+
name,
315+
options,
316+
resolver,
317+
} of normalizeConfigResolvers(configResolvers, sourceFile)) {
318+
if (!enable) {
319+
continue
320+
}
325321

326-
if (resolved?.found) {
327-
fileExistsCache.set(cacheKey, resolved.path)
328-
return resolved
322+
// if the resolver is `eslint-import-resolver-node`, we use the new `node` resolver first
323+
// and try `eslint-import-resolver-node` as fallback instead
324+
if (LEGACY_NODE_RESOLVERS.has(name)) {
325+
const resolverOptions = (options || {}) as NodeResolverOptions
326+
const resolved = legacyNodeResolve(
327+
resolverOptions,
328+
// TODO: enable the following in the next major
329+
// {
330+
// ...resolverOptions,
331+
// extensions:
332+
// resolverOptions.extensions || settings['import-x/extensions'],
333+
// },
334+
context,
335+
modulePath,
336+
sourceFile,
337+
)
338+
339+
if (resolved?.found) {
340+
fileExistsCache.set(cacheKey, resolved.path)
341+
return resolved
342+
}
343+
344+
if (!resolver) {
345+
continue
346+
}
329347
}
330348

331-
if (!resolver) {
349+
const resolved = setRuleContext(context, () =>
350+
resolveWithLegacyResolver(resolver, options, modulePath, sourceFile),
351+
)
352+
353+
if (!resolved?.found) {
332354
continue
333355
}
334-
}
335356

336-
const resolved = setRuleContext(context, () =>
337-
resolveWithLegacyResolver(resolver, options, modulePath, sourceFile),
338-
)
339-
340-
if (!resolved?.found) {
341-
continue
357+
// else, counts
358+
fileExistsCache.set(cacheKey, resolved.path as string | null)
359+
return resolved
342360
}
343-
344-
// else, counts
345-
fileExistsCache.set(cacheKey, resolved.path as string | null)
346-
return resolved
347361
}
348362
}
349363

test/fixtures/yarn-pnp/.yarn/releases/yarn-4.9.1.cjs renamed to test/fixtures/yarn-pnp/.yarn/releases/yarn-4.9.2.cjs

Lines changed: 273 additions & 279 deletions
Large diffs are not rendered by default.

test/fixtures/yarn-pnp/.yarnrc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
nodeLinker: pnp
22

3-
yarnPath: .yarn/releases/yarn-4.9.1.cjs
3+
yarnPath: .yarn/releases/yarn-4.9.2.cjs

test/fixtures/yarn-pnp/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import pnp from 'pnpapi'
22

33
import add from 'epix-oxc/add'
44

5-
console.log(add)
5+
import add2 from './__virtual__/whatever-123/0/add.js'
66

7+
console.log(add)
8+
console.log(add2)
79
console.log(pnp)

test/fixtures/yarn-pnp/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
{
22
"name": "yarn-pnp",
33
"type": "module",
4-
"packageManager": "yarn@4.9.1",
4+
"workspaces": [
5+
"test-package"
6+
],
7+
"packageManager": "yarn@4.9.2",
58
"exports": {
69
".": "./index.js",
710
"./add": "./add.js?custom"
811
},
912
"scripts": {
10-
"lint": "eslint ."
13+
"lint": "eslint && yarn workspace test-package lint"
1114
},
1215
"devDependencies": {
1316
"epix-oxc": "link:.",
14-
"eslint": "^9.24.0",
17+
"eslint": "^9.29.0",
1518
"eslint-plugin-import-x": "link:../../.."
1619
}
1720
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import js from '@eslint/js'
2+
import { importX } from 'eslint-plugin-import-x'
3+
import * as tsParser from '@typescript-eslint/parser'
4+
import { globalIgnores } from 'eslint/config'
5+
import globals from 'globals'
6+
7+
export default [
8+
globalIgnores(['.pnp.cjs', '.yarn']),
9+
js.configs.recommended,
10+
importX.flatConfigs.recommended,
11+
importX.flatConfigs.typescript,
12+
{
13+
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
14+
languageOptions: {
15+
parser: tsParser,
16+
ecmaVersion: 'latest',
17+
sourceType: 'module',
18+
globals: globals.node,
19+
},
20+
rules: {
21+
'import-x/no-dynamic-require': 'warn',
22+
'import-x/no-nodejs-modules': 'warn',
23+
},
24+
},
25+
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "test-package",
3+
"version": "1.0.0",
4+
"description": "A test project for ESLint configuration",
5+
"private": true,
6+
"scripts": {
7+
"demo": "tsx test.ts",
8+
"lint": "eslint"
9+
},
10+
"dependencies": {
11+
"nanoid": "^5.1.5"
12+
},
13+
"devDependencies": {
14+
"@eslint/js": "^9.29.0",
15+
"@types/node": "^22.15.32",
16+
"@typescript-eslint/parser": "^8.34.1",
17+
"eslint": "^9.29.0",
18+
"eslint-import-resolver-typescript": "^4.4.3",
19+
"eslint-plugin-import-x": "link:../../../..",
20+
"globals": "^16.2.0",
21+
"tsx": "^4.20.3",
22+
"typescript": "^5.8.3"
23+
}
24+
}

0 commit comments

Comments
 (0)