Skip to content

Commit 93ea130

Browse files
authored
feat: implement import-x resolver interface v3 (#326)
* feat: implement import-x resolver interface v3 * chore: add changeset * docs: update eslint flag config usage * test: fix test ci * chore: update changeset
1 parent e6256b7 commit 93ea130

21 files changed

+400
-19
lines changed

.changeset/metal-insects-trade.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
'eslint-import-resolver-typescript': minor
3+
---
4+
5+
This version has implemented the `eslint-plugin-import-x`'s v3 resolver interface. This allows you to use import/require to reference `eslint-import-resolver-typescript` directly in your ESLint flat config:
6+
7+
**Previously**
8+
9+
```js
10+
// eslint.config.js
11+
module.exports = {
12+
settings: {
13+
'import-x/resolver': {
14+
typescript: {
15+
alwaysTryTypes: true,
16+
},
17+
// or
18+
require.resolve('eslint-import-resolver-typescript'):
19+
alwaysTryTypes: true,
20+
}
21+
}
22+
}
23+
}
24+
```
25+
26+
**Now**
27+
28+
```js
29+
// eslint.config.js
30+
const {
31+
createTypeScriptImportResolver,
32+
} = require('eslint-import-resolver-typescript')
33+
34+
module.exports = {
35+
settings: {
36+
'import-x/resolver-next': [
37+
createTypeScriptImportResolver({
38+
alwaysTryTypes: true,
39+
}),
40+
],
41+
},
42+
}
43+
```
44+
45+
Note that this only works with `eslint-plugin-import-x@>=4.5.0`. You can't use `createTypeScriptImportResolver` with the older versions of `eslint-plugin-import-x` or any existing versions of `eslint-plugin-import`.

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"eslint.useFlatConfig": false
3+
}

README.md

+84
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,90 @@ yarn add -D eslint-plugin-import eslint-import-resolver-typescript
6262

6363
## Configuration
6464

65+
### `eslint.config.js`
66+
67+
If you are using `eslint-plugin-import-x@>=4.5.0`, you can use import/require to reference `eslint-import-resolver-typescript` directly in your ESLint flat config:
68+
69+
```js
70+
// eslint.config.js
71+
const {
72+
createTypeScriptImportResolver,
73+
} = require('eslint-import-resolver-typescript')
74+
75+
module.exports = [{
76+
settings: {
77+
"import/resolver-next": [
78+
createTypeScriptImportResolver({
79+
alwaysTryTypes: true, // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
80+
81+
// Choose from one of the "project" configs below or omit to use <root>/tsconfig.json by default
82+
83+
// use <root>/path/to/folder/tsconfig.json
84+
project: "path/to/folder",
85+
86+
// Multiple tsconfigs (Useful for monorepos)
87+
88+
// use a glob pattern
89+
project: "packages/*/tsconfig.json",
90+
91+
// use an array
92+
project: [
93+
"packages/module-a/tsconfig.json",
94+
"packages/module-b/tsconfig.json"
95+
],
96+
97+
// use an array of glob patterns
98+
project: [
99+
"packages/*/tsconfig.json",
100+
"other-packages/*/tsconfig.json"
101+
]
102+
}),
103+
];
104+
}
105+
}]
106+
```
107+
108+
But if you are using `eslint-plugin-import` or the older version of `eslint-plugin-import-x`, you can't use require/import:
109+
110+
```js
111+
// eslint.config.js
112+
module.exports = [
113+
{
114+
settings: {
115+
'import/resolvers': {
116+
typescript: {
117+
alwaysTryTypes: true, // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
118+
119+
// Choose from one of the "project" configs below or omit to use <root>/tsconfig.json by default
120+
121+
// use <root>/path/to/folder/tsconfig.json
122+
project: 'path/to/folder',
123+
124+
// Multiple tsconfigs (Useful for monorepos)
125+
126+
// use a glob pattern
127+
project: 'packages/*/tsconfig.json',
128+
129+
// use an array
130+
project: [
131+
'packages/module-a/tsconfig.json',
132+
'packages/module-b/tsconfig.json',
133+
],
134+
135+
// use an array of glob patterns
136+
project: [
137+
'packages/*/tsconfig.json',
138+
'other-packages/*/tsconfig.json',
139+
],
140+
},
141+
},
142+
},
143+
},
144+
]
145+
```
146+
147+
### `.eslintrc`
148+
65149
Add the following to your `.eslintrc` config:
66150

67151
```jsonc

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"prepare": "simple-git-hooks",
5252
"release": "changeset publish",
5353
"test": "run-p 'test:*'",
54+
"test:importXResolverV3": "eslint --config=tests/importXResolverV3/eslint.config.js tests/importXResolverV3",
5455
"test:multipleEslintrcs": "eslint --ext ts,tsx tests/multipleEslintrcs",
5556
"test:multipleTsconfigs": "eslint --ext ts,tsx tests/multipleTsconfigs",
5657
"test:withJsExtension": "node tests/withJsExtension/test.js && eslint --ext ts,tsx tests/withJsExtension",
@@ -78,13 +79,14 @@
7879
"@nolyfill/is-core-module": "1.0.39",
7980
"debug": "^4.3.7",
8081
"enhanced-resolve": "^5.15.0",
81-
"eslint-module-utils": "^2.8.1",
8282
"fast-glob": "^3.3.2",
8383
"get-tsconfig": "^4.7.5",
8484
"is-bun-module": "^1.0.2",
85-
"is-glob": "^4.0.3"
85+
"is-glob": "^4.0.3",
86+
"stable-hash": "^0.0.4"
8687
},
8788
"devDependencies": {
89+
"@1stg/eslint-config": "7",
8890
"@1stg/lib-config": "^12.0.1",
8991
"@changesets/changelog-github": "^0.5.0",
9092
"@changesets/cli": "^2.27.10",
@@ -99,6 +101,7 @@
99101
"eslint": "^8.57.1",
100102
"eslint-import-resolver-typescript": "link:.",
101103
"eslint-plugin-import": "npm:eslint-plugin-i@^2.29.1",
104+
"eslint-plugin-import-x": "^4.5.0",
102105
"lint-staged": "^13.3.0",
103106
"npm-run-all2": "^5.0.2",
104107
"prettier": "^2.8.8",

shim.d.ts

-4
This file was deleted.

src/index.ts

+36-9
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ import isNodeCoreModule from '@nolyfill/is-core-module'
55
import debug from 'debug'
66
import type { FileSystem, ResolveOptions, Resolver } from 'enhanced-resolve'
77
import enhancedResolve from 'enhanced-resolve'
8-
import { hashObject } from 'eslint-module-utils/hash.js'
98
import fg from 'fast-glob'
109
import { createPathsMatcher, getTsconfig } from 'get-tsconfig'
1110
import type { TsConfigResult } from 'get-tsconfig'
1211
import type { Version } from 'is-bun-module'
1312
import { isBunModule } from 'is-bun-module'
1413
import isGlob from 'is-glob'
14+
import stableHashExports from 'stable-hash'
1515

1616
const { globSync } = fg
1717

18+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- esmodule interop
19+
const stableHash = stableHashExports.default || stableHashExports
20+
1821
const IMPORTER_NAME = 'eslint-import-resolver-typescript'
1922

2023
const log = debug(IMPORTER_NAME)
@@ -114,10 +117,7 @@ let mappersCachedOptions: InternalResolverOptions
114117
let mappers: Array<((specifier: string) => string[]) | null> | undefined
115118

116119
let resolverCachedOptions: InternalResolverOptions
117-
let resolver: Resolver | undefined
118-
119-
const digestHashObject = (value: object | null | undefined) =>
120-
hashObject(value ?? {}).digest('hex')
120+
let cachedResolver: Resolver | undefined
121121

122122
/**
123123
* @param source the module to resolve; i.e './some-module'
@@ -129,13 +129,14 @@ export function resolve(
129129
source: string,
130130
file: string,
131131
options?: TsResolverOptions | null,
132+
resolver: Resolver | null = null,
132133
): {
133134
found: boolean
134135
path?: string | null
135136
} {
136137
if (
137138
!cachedOptions ||
138-
previousOptionsHash !== (optionsHash = digestHashObject(options))
139+
previousOptionsHash !== (optionsHash = stableHash(options))
139140
) {
140141
previousOptionsHash = optionsHash
141142
cachedOptions = {
@@ -152,9 +153,13 @@ export function resolve(
152153
}
153154
}
154155

155-
if (!resolver || resolverCachedOptions !== cachedOptions) {
156-
resolver = enhancedResolve.ResolverFactory.createResolver(cachedOptions)
157-
resolverCachedOptions = cachedOptions
156+
if (!resolver) {
157+
if (!cachedResolver || resolverCachedOptions !== cachedOptions) {
158+
cachedResolver =
159+
enhancedResolve.ResolverFactory.createResolver(cachedOptions)
160+
resolverCachedOptions = cachedOptions
161+
}
162+
resolver = cachedResolver
158163
}
159164

160165
log('looking for:', source)
@@ -229,6 +234,28 @@ export function resolve(
229234
}
230235
}
231236

237+
export function createTypeScriptImportResolver(
238+
options?: TsResolverOptions | null,
239+
) {
240+
const resolver = enhancedResolve.ResolverFactory.createResolver({
241+
...options,
242+
conditionNames: options?.conditionNames ?? defaultConditionNames,
243+
extensions: options?.extensions ?? defaultExtensions,
244+
extensionAlias: options?.extensionAlias ?? defaultExtensionAlias,
245+
mainFields: options?.mainFields ?? defaultMainFields,
246+
fileSystem: new enhancedResolve.CachedInputFileSystem(fileSystem, 5 * 1000),
247+
useSyncFileSystemCalls: true,
248+
})
249+
250+
return {
251+
interfaceVersion: 3,
252+
name: IMPORTER_NAME,
253+
resolve(source: string, file: string) {
254+
return resolve(source, file, options, resolver)
255+
},
256+
}
257+
}
258+
232259
/** Remove any trailing querystring from module id. */
233260
function removeQuerystring(id: string) {
234261
const querystringIndex = id.lastIndexOf('?')
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const path = require('path')
2+
const { createTypeScriptImportResolver } = require('../../lib/index.cjs')
3+
4+
const globPattern = './packages/*/tsconfig.json'
5+
6+
// in normal cases this is not needed because the __dirname would be the root
7+
const absoluteGlobPath = path.join(__dirname, globPattern)
8+
9+
module.exports = {
10+
...require('eslint-plugin-import-x').flatConfigs.typescript,
11+
settings: {
12+
...require('eslint-plugin-import-x').flatConfigs.typescript.settings,
13+
'import-x/resolver-next': [
14+
createTypeScriptImportResolver({
15+
project: absoluteGlobPath,
16+
alwaysTryTypes: true,
17+
}),
18+
],
19+
},
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// import relative
2+
import './tsImportee'
3+
import './tsxImportee'
4+
import './subfolder/tsImportee'
5+
import './subfolder/tsxImportee'
6+
7+
// import using tsconfig.json path mapping
8+
import 'folder/tsImportee'
9+
import 'folder/tsxImportee'
10+
import 'folder/subfolder/tsImportee'
11+
import 'folder/subfolder/tsxImportee'
12+
13+
// import from node_module
14+
import 'typescript'
15+
import 'dummy.js'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'yes'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'React Component'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'yes'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"jsx": "react",
5+
"paths": {
6+
"folder/*": ["*"],
7+
"*": ["../../../../node_modules/*"]
8+
}
9+
},
10+
"files": ["index.ts", "tsImportee.ts", "tsxImportee.tsx"]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'React Component'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// import relative
2+
import './tsImportee'
3+
import './tsxImportee'
4+
import './subfolder/tsImportee'
5+
import './subfolder/tsxImportee'
6+
7+
// import using tsconfig.json path mapping
8+
import 'folder/tsImportee'
9+
import 'folder/tsxImportee'
10+
import 'folder/subfolder/tsImportee'
11+
import 'folder/subfolder/tsxImportee'
12+
13+
// import from node_module
14+
import 'typescript'
15+
import 'dummy.js'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'yes'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'React Component'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'yes'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"jsx": "react",
5+
"paths": {
6+
"folder/*": ["*"],
7+
"*": ["../../../../node_modules/*"]
8+
}
9+
},
10+
"files": ["index.ts", "tsImportee.ts", "tsxImportee.tsx"]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'React Component'

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
"allowSyntheticDefaultImports": true,
77
"esModuleInterop": true
88
},
9-
"include": ["./src", "./shim.d.ts"]
9+
"include": ["./src"]
1010
}

0 commit comments

Comments
 (0)