Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/poor-dolls-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-import-x": patch
---

fix: pnp issue when used in a monorepo
11 changes: 9 additions & 2 deletions src/utils/module-require.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Module from 'node:module'
import Module, { createRequire } from 'node:module'
import path from 'node:path'

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

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

try {
// try relative to the current context
return createRequire(sourceFile)(p)
} catch {
//
}

// finally, try from here
return cjsRequire(p)
}
5 changes: 4 additions & 1 deletion src/utils/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ export function parse(
// require the parser relative to the main module (i.e., ESLint)
const parser =
typeof parserOrPath === 'string'
? moduleRequire<TSESLint.Parser.ParserModule>(parserOrPath)
? moduleRequire<TSESLint.Parser.ParserModule>(
parserOrPath,
context.physicalFilename,
)
: parserOrPath

// replicate bom strip and hashbang transform of ESLint
Expand Down
94 changes: 54 additions & 40 deletions src/utils/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
filepath: string | null,
cacheSettings: NormalizedCacheSettings,
strict?: boolean,
leaf: boolean = true,
): boolean {
// don't care if the FS is case-sensitive
if (CASE_SENSITIVE_FS) {
Expand Down Expand Up @@ -76,8 +77,12 @@
} else {
const filenames = fs.readdirSync(dir)
result = filenames.includes(parsedPath.base)
? fileExistsWithCaseSync(dir, cacheSettings, strict)
: false
? fileExistsWithCaseSync(dir, cacheSettings, strict, false)
: !leaf &&
// We tolerate case-insensitive matches if there are no case-insensitive matches.
// It'll fail anyway on the leaf node if the file truly doesn't exist (if it doesn't
// fail it's that we're probably working with a virtual in-memory filesystem).
!filenames.some(p => p.toLowerCase() === parsedPath.base.toLowerCase())
}
fileExistsCache.set(filepath, result)
return result
Expand Down Expand Up @@ -298,52 +303,61 @@
node: settings['import-x/resolve'],
} // backward compatibility

for (const { enable, name, options, resolver } of normalizeConfigResolvers(
configResolvers,
sourceFile,
)) {
if (!enable) {
continue
}
const sourceFiles =
context.physicalFilename === sourceFile
? [sourceFile]
: [context.physicalFilename, sourceFile]

// if the resolver is `eslint-import-resolver-node`, we use the new `node` resolver first
// and try `eslint-import-resolver-node` as fallback instead
if (LEGACY_NODE_RESOLVERS.has(name)) {
const resolverOptions = (options || {}) as NodeResolverOptions
const resolved = legacyNodeResolve(
resolverOptions,
// TODO: enable the following in the next major
// {
// ...resolverOptions,
// extensions:
// resolverOptions.extensions || settings['import-x/extensions'],
// },
context,
modulePath,
sourceFile,
)
for (const sourceFile of sourceFiles) {
for (const {
enable,
name,
options,
resolver,
} of normalizeConfigResolvers(configResolvers, sourceFile)) {
if (!enable) {
continue

Check warning on line 319 in src/utils/resolve.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/resolve.ts#L319

Added line #L319 was not covered by tests
}

if (resolved?.found) {
fileExistsCache.set(cacheKey, resolved.path)
return resolved
// if the resolver is `eslint-import-resolver-node`, we use the new `node` resolver first
// and try `eslint-import-resolver-node` as fallback instead
if (LEGACY_NODE_RESOLVERS.has(name)) {
const resolverOptions = (options || {}) as NodeResolverOptions
const resolved = legacyNodeResolve(
resolverOptions,
// TODO: enable the following in the next major
// {
// ...resolverOptions,
// extensions:
// resolverOptions.extensions || settings['import-x/extensions'],
// },
context,
modulePath,
sourceFile,
)

if (resolved?.found) {
fileExistsCache.set(cacheKey, resolved.path)
return resolved
}

if (!resolver) {
continue
}
}

if (!resolver) {
const resolved = setRuleContext(context, () =>
resolveWithLegacyResolver(resolver, options, modulePath, sourceFile),
)

if (!resolved?.found) {
continue
}
}

const resolved = setRuleContext(context, () =>
resolveWithLegacyResolver(resolver, options, modulePath, sourceFile),
)

if (!resolved?.found) {
continue
// else, counts
fileExistsCache.set(cacheKey, resolved.path as string | null)
return resolved
}

// else, counts
fileExistsCache.set(cacheKey, resolved.path as string | null)
return resolved
}
}

Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test/fixtures/yarn-pnp/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nodeLinker: pnp

yarnPath: .yarn/releases/yarn-4.9.1.cjs
yarnPath: .yarn/releases/yarn-4.9.2.cjs
4 changes: 3 additions & 1 deletion test/fixtures/yarn-pnp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import pnp from 'pnpapi'

import add from 'epix-oxc/add'

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

console.log(add)
console.log(add2)
console.log(pnp)
9 changes: 6 additions & 3 deletions test/fixtures/yarn-pnp/package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
{
"name": "yarn-pnp",
"type": "module",
"packageManager": "[email protected]",
"workspaces": [
"test-package"
],
"packageManager": "[email protected]",
"exports": {
".": "./index.js",
"./add": "./add.js?custom"
},
"scripts": {
"lint": "eslint ."
"lint": "eslint && yarn workspace test-package lint"
},
"devDependencies": {
"epix-oxc": "link:.",
"eslint": "^9.24.0",
"eslint": "^9.29.0",
"eslint-plugin-import-x": "link:../../.."
}
}
25 changes: 25 additions & 0 deletions test/fixtures/yarn-pnp/test-package/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import js from '@eslint/js'
import { importX } from 'eslint-plugin-import-x'
import * as tsParser from '@typescript-eslint/parser'
import { globalIgnores } from 'eslint/config'
import globals from 'globals'

export default [
globalIgnores(['.pnp.cjs', '.yarn']),
js.configs.recommended,
importX.flatConfigs.recommended,
importX.flatConfigs.typescript,
{
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
languageOptions: {
parser: tsParser,
ecmaVersion: 'latest',
sourceType: 'module',
globals: globals.node,
},
rules: {
'import-x/no-dynamic-require': 'warn',
'import-x/no-nodejs-modules': 'warn',
},
},
]
24 changes: 24 additions & 0 deletions test/fixtures/yarn-pnp/test-package/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "test-package",
"version": "1.0.0",
"description": "A test project for ESLint configuration",
"private": true,
"scripts": {
"demo": "tsx test.ts",
"lint": "eslint"
},
"dependencies": {
"nanoid": "^5.1.5"
},
"devDependencies": {
"@eslint/js": "^9.29.0",
"@types/node": "^22.15.32",
"@typescript-eslint/parser": "^8.34.1",
"eslint": "^9.29.0",
"eslint-import-resolver-typescript": "^4.4.3",
"eslint-plugin-import-x": "link:../../../..",
"globals": "^16.2.0",
"tsx": "^4.20.3",
"typescript": "^5.8.3"
}
}
3 changes: 3 additions & 0 deletions test/fixtures/yarn-pnp/test-package/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { nanoid } from 'nanoid'

console.log('nanoid', nanoid())
Loading