From c4cb88dabed0aa9a7b5fe1adcdf2430d7c4d01c5 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Wed, 25 Jun 2025 10:30:02 -0400 Subject: [PATCH 1/4] test: add failing test in preparation for a fix --- tests/e2e/__snapshots__/e2e.spec.ts.snap | 14 ++++++ .../eslint.config.js | 44 +++++++++++++++++++ .../filesWithDifferentOptions/src/a.bar.js | 1 + .../filesWithDifferentOptions/src/a.foo.js | 1 + .../filesWithDifferentOptions/src/x.foo.js | 1 + .../filesWithDifferentOptions/src/y.bar.js | 1 + .../filesWithDifferentOptions/tsconfig.json | 1 + 7 files changed, 63 insertions(+) create mode 100644 tests/e2e/filesWithDifferentOptions/eslint.config.js create mode 100644 tests/e2e/filesWithDifferentOptions/src/a.bar.js create mode 100644 tests/e2e/filesWithDifferentOptions/src/a.foo.js create mode 100644 tests/e2e/filesWithDifferentOptions/src/x.foo.js create mode 100644 tests/e2e/filesWithDifferentOptions/src/y.bar.js create mode 100644 tests/e2e/filesWithDifferentOptions/tsconfig.json diff --git a/tests/e2e/__snapshots__/e2e.spec.ts.snap b/tests/e2e/__snapshots__/e2e.spec.ts.snap index 622d64a..43d51f2 100644 --- a/tests/e2e/__snapshots__/e2e.spec.ts.snap +++ b/tests/e2e/__snapshots__/e2e.spec.ts.snap @@ -32,6 +32,20 @@ exports[`e2e cases > should exec eslint successfully > dotProject 1`] = ` } `; +exports[`e2e cases > should exec eslint successfully > filesWithDifferentOptions 1`] = ` +{ + "exitCode": 1, + "stderr": "", + "stdout": " +/tests/e2e/filesWithDifferentOptions/src/a.foo.js + 1:15 error Unable to resolve path to module './x' import-x/no-unresolved + +✖ 1 problem (1 error, 0 warnings) + +", +} +`; + exports[`e2e cases > should exec eslint successfully > importXResolverV3 1`] = ` { "exitCode": 0, diff --git a/tests/e2e/filesWithDifferentOptions/eslint.config.js b/tests/e2e/filesWithDifferentOptions/eslint.config.js new file mode 100644 index 0000000..c1adf02 --- /dev/null +++ b/tests/e2e/filesWithDifferentOptions/eslint.config.js @@ -0,0 +1,44 @@ +import { defineConfig, globalIgnores } from 'eslint/config' +import { defaultExtensions } from 'eslint-import-resolver-typescript' +import importX, { flatConfigs } from 'eslint-plugin-import-x' + +export default defineConfig( + globalIgnores(['eslint.config.js']), + { + files: ['**/*.foo.js', '**/*.bar.js'], + plugins: { + 'import-x': importX, + }, + settings: { + ...flatConfigs.typescript.settings, + 'import-x/resolver': { + typescript: {}, + }, + }, + rules: { + 'import-x/no-unresolved': 'error', + }, + }, + // .foo.js files should prefer importing other .foo.js files. + { + files: ['**/*.foo.js'], + settings: { + 'import-x/resolver': { + typescript: { + extensions: ['.foo.js', ...defaultExtensions], + }, + }, + }, + }, + // .bar.js files should prefer importing other .bar.js files. + { + files: ['**/*.bar.js'], + settings: { + 'import-x/resolver': { + typescript: { + extensions: ['.bar.js', ...defaultExtensions], + }, + }, + }, + }, +) diff --git a/tests/e2e/filesWithDifferentOptions/src/a.bar.js b/tests/e2e/filesWithDifferentOptions/src/a.bar.js new file mode 100644 index 0000000..ec54c7f --- /dev/null +++ b/tests/e2e/filesWithDifferentOptions/src/a.bar.js @@ -0,0 +1 @@ +import y from './y' diff --git a/tests/e2e/filesWithDifferentOptions/src/a.foo.js b/tests/e2e/filesWithDifferentOptions/src/a.foo.js new file mode 100644 index 0000000..849aac5 --- /dev/null +++ b/tests/e2e/filesWithDifferentOptions/src/a.foo.js @@ -0,0 +1 @@ +import x from './x' diff --git a/tests/e2e/filesWithDifferentOptions/src/x.foo.js b/tests/e2e/filesWithDifferentOptions/src/x.foo.js new file mode 100644 index 0000000..80bf0a9 --- /dev/null +++ b/tests/e2e/filesWithDifferentOptions/src/x.foo.js @@ -0,0 +1 @@ +export const x = 'x' diff --git a/tests/e2e/filesWithDifferentOptions/src/y.bar.js b/tests/e2e/filesWithDifferentOptions/src/y.bar.js new file mode 100644 index 0000000..6180eb9 --- /dev/null +++ b/tests/e2e/filesWithDifferentOptions/src/y.bar.js @@ -0,0 +1 @@ +export const y = 'y' diff --git a/tests/e2e/filesWithDifferentOptions/tsconfig.json b/tests/e2e/filesWithDifferentOptions/tsconfig.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/e2e/filesWithDifferentOptions/tsconfig.json @@ -0,0 +1 @@ +{} From aba5ddf14d09393fcbc55cc3839c0682fa7bc3ab Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Wed, 25 Jun 2025 10:59:12 -0400 Subject: [PATCH 2/4] fix: only cache filename lookup, not options Presumably to avoid excessive stat calls, the return value of `normalizeOptions()` is cached for each input tsconfig file. But the return value also depends on the input `options`: eslint configuration may pass different options for the same tsconfig file. Instead, cache only the result of the `tryFile()` call. --- .changeset/slick-breads-feel.md | 5 +++++ src/normalize-options.ts | 22 +++++++++------------- tests/e2e/__snapshots__/e2e.spec.ts.snap | 10 ++-------- 3 files changed, 16 insertions(+), 21 deletions(-) create mode 100644 .changeset/slick-breads-feel.md diff --git a/.changeset/slick-breads-feel.md b/.changeset/slick-breads-feel.md new file mode 100644 index 0000000..fcf485e --- /dev/null +++ b/.changeset/slick-breads-feel.md @@ -0,0 +1,5 @@ +--- +"eslint-import-resolver-typescript": patch +--- + +fix: only cache filename lookup, not options diff --git a/src/normalize-options.ts b/src/normalize-options.ts index ba675eb..2921224 100644 --- a/src/normalize-options.ts +++ b/src/normalize-options.ts @@ -16,7 +16,7 @@ import type { TypeScriptResolverOptions } from './types.js' export let defaultConfigFile: string -const configFileMapping = new Map() +const configFileMapping = new Map() let warned: boolean | undefined @@ -77,17 +77,17 @@ export function normalizeOptions( } if (configFile) { - const cachedOptions = configFileMapping.get(configFile) - if (cachedOptions) { - log('using cached options for', configFile) - return cachedOptions + let cachedConfigFile: string | undefined = configFileMapping.get(configFile) + if (cachedConfigFile) { + log('using cached config file for', configFile) + configFile = cachedConfigFile + } else if (!ensured && configFile !== defaultConfigFile) { + cachedConfigFile = tryFile(DEFAULT_TRY_PATHS, false, configFile) + configFileMapping.set(configFile, cachedConfigFile) + configFile = cachedConfigFile } } - if (!ensured && configFile && configFile !== defaultConfigFile) { - configFile = tryFile(DEFAULT_TRY_PATHS, false, configFile) - } - options = { conditionNames: defaultConditionNames, extensions: defaultExtensions, @@ -100,9 +100,5 @@ export function normalizeOptions( : undefined, } - if (configFile) { - configFileMapping.set(configFile, options) - } - return options } diff --git a/tests/e2e/__snapshots__/e2e.spec.ts.snap b/tests/e2e/__snapshots__/e2e.spec.ts.snap index 43d51f2..b48beee 100644 --- a/tests/e2e/__snapshots__/e2e.spec.ts.snap +++ b/tests/e2e/__snapshots__/e2e.spec.ts.snap @@ -34,15 +34,9 @@ exports[`e2e cases > should exec eslint successfully > dotProject 1`] = ` exports[`e2e cases > should exec eslint successfully > filesWithDifferentOptions 1`] = ` { - "exitCode": 1, + "exitCode": 0, "stderr": "", - "stdout": " -/tests/e2e/filesWithDifferentOptions/src/a.foo.js - 1:15 error Unable to resolve path to module './x' import-x/no-unresolved - -✖ 1 problem (1 error, 0 warnings) - -", + "stdout": "", } `; From 50bd16e2d16180b7eb1a978d32856a0d0798231f Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Wed, 25 Jun 2025 12:49:28 -0400 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20hash=20the=20options=20for=20the=20c?= =?UTF-8?q?ache=20instead.=20=F0=9F=A4=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/slick-breads-feel.md | 2 +- src/normalize-options.ts | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.changeset/slick-breads-feel.md b/.changeset/slick-breads-feel.md index fcf485e..2821257 100644 --- a/.changeset/slick-breads-feel.md +++ b/.changeset/slick-breads-feel.md @@ -2,4 +2,4 @@ "eslint-import-resolver-typescript": patch --- -fix: only cache filename lookup, not options +fix: include options hash in cache key for options normalization diff --git a/src/normalize-options.ts b/src/normalize-options.ts index 2921224..02525fd 100644 --- a/src/normalize-options.ts +++ b/src/normalize-options.ts @@ -1,3 +1,4 @@ +import { stableHash } from 'stable-hash-x' import { globSync, isDynamicPattern } from 'tinyglobby' import type { TsconfigOptions } from 'unrs-resolver' @@ -16,7 +17,7 @@ import type { TypeScriptResolverOptions } from './types.js' export let defaultConfigFile: string -const configFileMapping = new Map() +const configFileMapping = new Map() let warned: boolean | undefined @@ -76,18 +77,24 @@ export function normalizeOptions( ensured = true } + const optionsHash = stableHash(options) if (configFile) { - let cachedConfigFile: string | undefined = configFileMapping.get(configFile) - if (cachedConfigFile) { - log('using cached config file for', configFile) - configFile = cachedConfigFile - } else if (!ensured && configFile !== defaultConfigFile) { - cachedConfigFile = tryFile(DEFAULT_TRY_PATHS, false, configFile) - configFileMapping.set(configFile, cachedConfigFile) - configFile = cachedConfigFile + const cachedOptions = configFileMapping.get(`${configFile}\0${optionsHash}`) + if (cachedOptions) { + log( + 'using cached options for', + configFile, + 'with options hash', + optionsHash, + ) + return cachedOptions } } + if (!ensured && configFile && configFile !== defaultConfigFile) { + configFile = tryFile(DEFAULT_TRY_PATHS, false, configFile) + } + options = { conditionNames: defaultConditionNames, extensions: defaultExtensions, @@ -100,5 +107,9 @@ export function normalizeOptions( : undefined, } + if (configFile) { + configFileMapping.set(`${configFile}\0${optionsHash}`, options) + } + return options } From 769f6a66ea1655ac5ee492f7b4920cdfcff5fd14 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Wed, 25 Jun 2025 12:58:24 -0400 Subject: [PATCH 4/4] fix: requested changes --- src/normalize-options.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/normalize-options.ts b/src/normalize-options.ts index 02525fd..11b96a5 100644 --- a/src/normalize-options.ts +++ b/src/normalize-options.ts @@ -78,15 +78,11 @@ export function normalizeOptions( } const optionsHash = stableHash(options) + const cacheKey = `${configFile}\0${optionsHash}` if (configFile) { - const cachedOptions = configFileMapping.get(`${configFile}\0${optionsHash}`) + const cachedOptions = configFileMapping.get(cacheKey) if (cachedOptions) { - log( - 'using cached options for', - configFile, - 'with options hash', - optionsHash, - ) + log('using cached options for', configFile, 'with options', options) return cachedOptions } } @@ -108,7 +104,7 @@ export function normalizeOptions( } if (configFile) { - configFileMapping.set(`${configFile}\0${optionsHash}`, options) + configFileMapping.set(cacheKey, options) } return options