diff --git a/.changeset/beige-gorillas-hunt.md b/.changeset/beige-gorillas-hunt.md new file mode 100644 index 0000000..e896184 --- /dev/null +++ b/.changeset/beige-gorillas-hunt.md @@ -0,0 +1,5 @@ +--- +"eslint-import-resolver-typescript": patch +--- + +fix: support multiple matching ts paths diff --git a/.eslintrc b/.eslintrc index 19106e5..95a3175 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,8 @@ "node": true }, "parserOptions": { - "ecmaVersion": "latest" + "ecmaVersion": "latest", + "sourceType": "module" }, "extends": [ "plugin:prettier/recommended", diff --git a/.nvmrc b/.nvmrc index 1fc5166..9be0c70 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.20.6 +18.20.7 diff --git a/package.json b/package.json index e3aabd3..54b36b0 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "test:dotInclude": "eslint --ext ts,tsx tests/dotInclude --ignore-pattern \"!.dot\"", "test:dotPaths": "eslint --ext ts,tsx tests/dotPaths --ignore-pattern \"!.dot\"", "test:dotProject": "eslint --ext ts,tsx tests/dotProject --ignore-pattern \"!.dot\"", - "test:importXResolverV3": "eslint --config=tests/importXResolverV3/eslint.config.js tests/importXResolverV3", + "test:importXResolverV3": "cross-env ESLINT_USE_FLAT_CONFIG=true eslint --config=tests/importXResolverV3/eslint.config.js tests/importXResolverV3", "test:multipleEslintrcs": "eslint --ext ts,tsx tests/multipleEslintrcs", "test:multipleTsconfigs": "eslint --ext ts,tsx tests/multipleTsconfigs", "test:withJsExtension": "node tests/withJsExtension/test.js && eslint --ext ts,tsx tests/withJsExtension", @@ -98,6 +98,7 @@ "@types/debug": "^4.1.12", "@types/node": "^18.19.78", "@types/unist": "^2.0.11", + "cross-env": "^7.0.3", "dummy.js": "link:dummy.js", "eslint": "^8.57.1", "eslint-import-resolver-typescript": "link:.", @@ -111,7 +112,7 @@ "size-limit": "^11.0.0", "size-limit-preset-node-lib": "^0.3.0", "type-coverage": "^2.27.0", - "typescript": "^5.3.2" + "typescript": "~5.1.0" }, "resolutions": { "eslint-import-resolver-typescript": "link:.", diff --git a/src/index.ts b/src/index.ts index bc92c25..1287aef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -181,22 +181,30 @@ export function resolve( initMappers(cachedOptions) - const mappedPath = getMappedPath(source, file, cachedOptions.extensions, true) - if (mappedPath) { - log('matched ts path:', mappedPath) + let mappedPaths = getMappedPaths(source, file, cachedOptions.extensions, true) + + if (mappedPaths.length > 0) { + log('matched ts path:', ...mappedPaths) + } else { + mappedPaths = [source] } // note that even if we map the path, we still need to do a final resolve - let foundNodePath: string | null - try { - foundNodePath = - resolver.resolveSync( + let foundNodePath: string | undefined + for (const mappedPath of mappedPaths) { + try { + const resolved = resolver.resolveSync( {}, path.dirname(path.resolve(file)), - mappedPath ?? source, - ) || null - } catch { - foundNodePath = null + mappedPath, + ) + if (resolved) { + foundNodePath = resolved + break + } + } catch { + log('failed to resolve with', mappedPath) + } } // naive attempt at `@types/*` resolution, @@ -286,16 +294,16 @@ const isModule = (modulePath?: string | undefined): modulePath is string => { * @returns The mapped path of the module or undefined */ // eslint-disable-next-line sonarjs/cognitive-complexity -function getMappedPath( +function getMappedPaths( source: string, file: string, extensions: string[] = defaultExtensions, retry?: boolean, -): string | undefined { +): string[] { const originalExtensions = extensions extensions = ['', ...extensions] - let paths: Array | undefined = [] + let paths: string[] = [] if (RELATIVE_PATH_PATTERN.test(source)) { const resolved = path.resolve(path.dirname(file), source) @@ -341,34 +349,35 @@ function getMappedPath( const tsExt = jsExt.replace('js', 'ts') const basename = source.replace(JS_EXT_PATTERN, '') - const resolved = - getMappedPath(basename + tsExt, file) || - getMappedPath( - basename + '.d' + (tsExt === '.tsx' ? '.ts' : tsExt), - file, - ) + const mappedPaths = getMappedPaths(basename + tsExt, file) - if (resolved) { + const resolved = + mappedPaths.length > 0 + ? mappedPaths + : getMappedPaths( + basename + '.d' + (tsExt === '.tsx' ? '.ts' : tsExt), + file, + ) + + if (resolved.length > 0) { return resolved } } for (const ext of extensions) { + const mappedPaths = isJs ? [] : getMappedPaths(source + ext, file) const resolved = - (isJs ? null : getMappedPath(source + ext, file)) || - getMappedPath(source + `/index${ext}`, file) + mappedPaths.length > 0 + ? mappedPaths + : getMappedPaths(source + `/index${ext}`, file) - if (resolved) { + if (resolved.length > 0) { return resolved } } } - if (paths.length > 1) { - log('found multiple matching ts paths:', paths) - } - - return paths[0] + return paths } // eslint-disable-next-line sonarjs/cognitive-complexity diff --git a/tests/importXResolverV3/eslint.config.js b/tests/importXResolverV3/eslint.config.js index 70a0e56..4fe2fdd 100644 --- a/tests/importXResolverV3/eslint.config.js +++ b/tests/importXResolverV3/eslint.config.js @@ -1,20 +1,31 @@ const path = require('path') -const { createTypeScriptImportResolver } = require('../../lib/index.cjs') +const importX = require('eslint-plugin-import-x') + +const { createTypeScriptImportResolver } = require('../..') const globPattern = './packages/*/tsconfig.json' // in normal cases this is not needed because the __dirname would be the root const absoluteGlobPath = path.join(__dirname, globPattern) -module.exports = { - ...require('eslint-plugin-import-x').flatConfigs.typescript, - settings: { - ...require('eslint-plugin-import-x').flatConfigs.typescript.settings, - 'import-x/resolver-next': [ - createTypeScriptImportResolver({ - project: absoluteGlobPath, - alwaysTryTypes: true, - }), - ], - }, -} +const base = require('../baseEslintConfig.cjs')() + +module.exports = + // don't run on node 16 because lacking of `structuredClone` + +process.versions.node.split('.')[0] <= 16 + ? {} + : { + files: ['**/*.ts', '**/*.tsx'], + plugins: { + import: importX, + }, + settings: { + ...importX.flatConfigs.typescript.settings, + 'import-x/resolver-next': [ + createTypeScriptImportResolver({ + project: absoluteGlobPath, + }), + ], + }, + rules: base.rules, + } diff --git a/yarn.lock b/yarn.lock index 56478d0..841bba1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2268,14 +2268,7 @@ __metadata: languageName: node linkType: hard -"@changesets/types@npm:^6.0.0": - version: 6.0.0 - resolution: "@changesets/types@npm:6.0.0" - checksum: 214c58ff3e3da019c578b94815ec6748729a38b665d950acddf53f3a23073ac7a57dce45812c4bec0cbcd6902c84a482c804457d4c903602005b2399de8a4021 - languageName: node - linkType: hard - -"@changesets/types@npm:^6.1.0": +"@changesets/types@npm:^6.0.0, @changesets/types@npm:^6.1.0": version: 6.1.0 resolution: "@changesets/types@npm:6.1.0" checksum: 2dcd00712cb85d0c53afdd8d0e856b4bf9c0ce8dc36c838c918d44799aacd9ba8659b9ff610ff92b94fc03c8fd2b52c5b05418fcf8a1bd138cd9182414ede373 @@ -3731,16 +3724,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.0.0": - version: 18.19.74 - resolution: "@types/node@npm:18.19.74" - dependencies: - undici-types: "npm:~5.26.4" - checksum: 2306bd0b41cdd528b890b210b96f287a5b5035c128f62636057d6616bd612b3f53d32d77f7e76ef41a9f130ea691e6980e6d5942dd625df05d3a641764fddb78 - languageName: node - linkType: hard - -"@types/node@npm:^18.19.78": +"@types/node@npm:^18.0.0, @types/node@npm:^18.19.78": version: 18.19.78 resolution: "@types/node@npm:18.19.78" dependencies: @@ -5373,6 +5357,18 @@ __metadata: languageName: node linkType: hard +"cross-env@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-env@npm:7.0.3" + dependencies: + cross-spawn: "npm:^7.0.1" + bin: + cross-env: src/bin/cross-env.js + cross-env-shell: src/bin/cross-env-shell.js + checksum: e99911f0d31c20e990fd92d6fd001f4b01668a303221227cc5cb42ed155f086351b1b3bd2699b200e527ab13011b032801f8ce638e6f09f854bdf744095e604c + languageName: node + linkType: hard + "cross-spawn@npm:^6.0.5": version: 6.0.5 resolution: "cross-spawn@npm:6.0.5" @@ -5386,7 +5382,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -6169,6 +6165,7 @@ __metadata: "@types/debug": "npm:^4.1.12" "@types/node": "npm:^18.19.78" "@types/unist": "npm:^2.0.11" + cross-env: "npm:^7.0.3" debug: "npm:^4.3.7" dummy.js: "link:dummy.js" enhanced-resolve: "npm:^5.15.0" @@ -6188,7 +6185,7 @@ __metadata: stable-hash: "npm:^0.0.4" tinyglobby: "npm:^0.2.12" type-coverage: "npm:^2.27.0" - typescript: "npm:^5.3.2" + typescript: "npm:~5.1.0" peerDependencies: eslint: "*" eslint-plugin-import: "*" @@ -13873,6 +13870,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:~5.1.0": + version: 5.1.6 + resolution: "typescript@npm:5.1.6" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: f347cde665cf43dc4c1c7d9821c7d9bbec3c3914f4bdd82ee490e9fb9f6d99036ed8666463b6a192dd005eeef333c5087d5931bdd51ec853436ff9a670a7417e + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A^4.6.4 || ^5.2.2#optional!builtin, typescript@patch:typescript@npm%3A^5.3.2#optional!builtin": version: 5.3.2 resolution: "typescript@patch:typescript@npm%3A5.3.2#optional!builtin::version=5.3.2&hash=e012d7" @@ -13883,6 +13890,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A~5.1.0#optional!builtin": + version: 5.1.6 + resolution: "typescript@patch:typescript@npm%3A5.1.6#optional!builtin::version=5.1.6&hash=5da071" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: f5481fa3ba0eee8970f46708d13c05650a865ad093b586fc9573f425c64c57ca97e3308e110bb528deb3ccebe83f6fd7b5a8ac90018038da96326a9ccdf8e77c + languageName: node + linkType: hard + "unassert@npm:^2.0.0, unassert@npm:^2.0.2": version: 2.0.2 resolution: "unassert@npm:2.0.2"