Skip to content

Commit

Permalink
feat: implement import-x resolver interface v3 (#326)
Browse files Browse the repository at this point in the history
* feat: implement import-x resolver interface v3

* chore: add changeset

* docs: update eslint flag config usage

* test: fix test ci

* chore: update changeset
  • Loading branch information
SukkaW authored Dec 3, 2024
1 parent e6256b7 commit 93ea130
Show file tree
Hide file tree
Showing 21 changed files with 400 additions and 19 deletions.
45 changes: 45 additions & 0 deletions .changeset/metal-insects-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
'eslint-import-resolver-typescript': minor
---

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:

**Previously**

```js
// eslint.config.js
module.exports = {
settings: {
'import-x/resolver': {
typescript: {
alwaysTryTypes: true,
},
// or
require.resolve('eslint-import-resolver-typescript'):
alwaysTryTypes: true,
}
}
}
}
```

**Now**

```js
// eslint.config.js
const {
createTypeScriptImportResolver,
} = require('eslint-import-resolver-typescript')

module.exports = {
settings: {
'import-x/resolver-next': [
createTypeScriptImportResolver({
alwaysTryTypes: true,
}),
],
},
}
```

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`.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eslint.useFlatConfig": false
}
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,90 @@ yarn add -D eslint-plugin-import eslint-import-resolver-typescript

## Configuration

### `eslint.config.js`

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:

```js
// eslint.config.js
const {
createTypeScriptImportResolver,
} = require('eslint-import-resolver-typescript')

module.exports = [{
settings: {
"import/resolver-next": [
createTypeScriptImportResolver({
alwaysTryTypes: true, // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`

// Choose from one of the "project" configs below or omit to use <root>/tsconfig.json by default

// use <root>/path/to/folder/tsconfig.json
project: "path/to/folder",

// Multiple tsconfigs (Useful for monorepos)

// use a glob pattern
project: "packages/*/tsconfig.json",

// use an array
project: [
"packages/module-a/tsconfig.json",
"packages/module-b/tsconfig.json"
],

// use an array of glob patterns
project: [
"packages/*/tsconfig.json",
"other-packages/*/tsconfig.json"
]
}),
];
}
}]
```

But if you are using `eslint-plugin-import` or the older version of `eslint-plugin-import-x`, you can't use require/import:

```js
// eslint.config.js
module.exports = [
{
settings: {
'import/resolvers': {
typescript: {
alwaysTryTypes: true, // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`

// Choose from one of the "project" configs below or omit to use <root>/tsconfig.json by default

// use <root>/path/to/folder/tsconfig.json
project: 'path/to/folder',

// Multiple tsconfigs (Useful for monorepos)

// use a glob pattern
project: 'packages/*/tsconfig.json',

// use an array
project: [
'packages/module-a/tsconfig.json',
'packages/module-b/tsconfig.json',
],

// use an array of glob patterns
project: [
'packages/*/tsconfig.json',
'other-packages/*/tsconfig.json',
],
},
},
},
},
]
```

### `.eslintrc`

Add the following to your `.eslintrc` config:

```jsonc
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"prepare": "simple-git-hooks",
"release": "changeset publish",
"test": "run-p 'test:*'",
"test:importXResolverV3": "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",
Expand Down Expand Up @@ -78,13 +79,14 @@
"@nolyfill/is-core-module": "1.0.39",
"debug": "^4.3.7",
"enhanced-resolve": "^5.15.0",
"eslint-module-utils": "^2.8.1",
"fast-glob": "^3.3.2",
"get-tsconfig": "^4.7.5",
"is-bun-module": "^1.0.2",
"is-glob": "^4.0.3"
"is-glob": "^4.0.3",
"stable-hash": "^0.0.4"
},
"devDependencies": {
"@1stg/eslint-config": "7",
"@1stg/lib-config": "^12.0.1",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.10",
Expand All @@ -99,6 +101,7 @@
"eslint": "^8.57.1",
"eslint-import-resolver-typescript": "link:.",
"eslint-plugin-import": "npm:eslint-plugin-i@^2.29.1",
"eslint-plugin-import-x": "^4.5.0",
"lint-staged": "^13.3.0",
"npm-run-all2": "^5.0.2",
"prettier": "^2.8.8",
Expand Down
4 changes: 0 additions & 4 deletions shim.d.ts

This file was deleted.

45 changes: 36 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import isNodeCoreModule from '@nolyfill/is-core-module'
import debug from 'debug'
import type { FileSystem, ResolveOptions, Resolver } from 'enhanced-resolve'
import enhancedResolve from 'enhanced-resolve'
import { hashObject } from 'eslint-module-utils/hash.js'
import fg from 'fast-glob'
import { createPathsMatcher, getTsconfig } from 'get-tsconfig'
import type { TsConfigResult } from 'get-tsconfig'
import type { Version } from 'is-bun-module'
import { isBunModule } from 'is-bun-module'
import isGlob from 'is-glob'
import stableHashExports from 'stable-hash'

const { globSync } = fg

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- esmodule interop
const stableHash = stableHashExports.default || stableHashExports

const IMPORTER_NAME = 'eslint-import-resolver-typescript'

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

let resolverCachedOptions: InternalResolverOptions
let resolver: Resolver | undefined

const digestHashObject = (value: object | null | undefined) =>
hashObject(value ?? {}).digest('hex')
let cachedResolver: Resolver | undefined

/**
* @param source the module to resolve; i.e './some-module'
Expand All @@ -129,13 +129,14 @@ export function resolve(
source: string,
file: string,
options?: TsResolverOptions | null,
resolver: Resolver | null = null,
): {
found: boolean
path?: string | null
} {
if (
!cachedOptions ||
previousOptionsHash !== (optionsHash = digestHashObject(options))
previousOptionsHash !== (optionsHash = stableHash(options))
) {
previousOptionsHash = optionsHash
cachedOptions = {
Expand All @@ -152,9 +153,13 @@ export function resolve(
}
}

if (!resolver || resolverCachedOptions !== cachedOptions) {
resolver = enhancedResolve.ResolverFactory.createResolver(cachedOptions)
resolverCachedOptions = cachedOptions
if (!resolver) {
if (!cachedResolver || resolverCachedOptions !== cachedOptions) {
cachedResolver =
enhancedResolve.ResolverFactory.createResolver(cachedOptions)
resolverCachedOptions = cachedOptions
}
resolver = cachedResolver
}

log('looking for:', source)
Expand Down Expand Up @@ -229,6 +234,28 @@ export function resolve(
}
}

export function createTypeScriptImportResolver(
options?: TsResolverOptions | null,
) {
const resolver = enhancedResolve.ResolverFactory.createResolver({
...options,
conditionNames: options?.conditionNames ?? defaultConditionNames,
extensions: options?.extensions ?? defaultExtensions,
extensionAlias: options?.extensionAlias ?? defaultExtensionAlias,
mainFields: options?.mainFields ?? defaultMainFields,
fileSystem: new enhancedResolve.CachedInputFileSystem(fileSystem, 5 * 1000),
useSyncFileSystemCalls: true,
})

return {
interfaceVersion: 3,
name: IMPORTER_NAME,
resolve(source: string, file: string) {
return resolve(source, file, options, resolver)
},
}
}

/** Remove any trailing querystring from module id. */
function removeQuerystring(id: string) {
const querystringIndex = id.lastIndexOf('?')
Expand Down
20 changes: 20 additions & 0 deletions tests/importXResolverV3/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const path = require('path')
const { createTypeScriptImportResolver } = require('../../lib/index.cjs')

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,
}),
],
},
}
15 changes: 15 additions & 0 deletions tests/importXResolverV3/packages/module-a/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// import relative
import './tsImportee'
import './tsxImportee'
import './subfolder/tsImportee'
import './subfolder/tsxImportee'

// import using tsconfig.json path mapping
import 'folder/tsImportee'
import 'folder/tsxImportee'
import 'folder/subfolder/tsImportee'
import 'folder/subfolder/tsxImportee'

// import from node_module
import 'typescript'
import 'dummy.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'yes'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'React Component'
1 change: 1 addition & 0 deletions tests/importXResolverV3/packages/module-a/tsImportee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'yes'
11 changes: 11 additions & 0 deletions tests/importXResolverV3/packages/module-a/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"baseUrl": ".",
"jsx": "react",
"paths": {
"folder/*": ["*"],
"*": ["../../../../node_modules/*"]
}
},
"files": ["index.ts", "tsImportee.ts", "tsxImportee.tsx"]
}
1 change: 1 addition & 0 deletions tests/importXResolverV3/packages/module-a/tsxImportee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'React Component'
15 changes: 15 additions & 0 deletions tests/importXResolverV3/packages/module-b/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// import relative
import './tsImportee'
import './tsxImportee'
import './subfolder/tsImportee'
import './subfolder/tsxImportee'

// import using tsconfig.json path mapping
import 'folder/tsImportee'
import 'folder/tsxImportee'
import 'folder/subfolder/tsImportee'
import 'folder/subfolder/tsxImportee'

// import from node_module
import 'typescript'
import 'dummy.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'yes'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'React Component'
1 change: 1 addition & 0 deletions tests/importXResolverV3/packages/module-b/tsImportee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'yes'
11 changes: 11 additions & 0 deletions tests/importXResolverV3/packages/module-b/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"baseUrl": ".",
"jsx": "react",
"paths": {
"folder/*": ["*"],
"*": ["../../../../node_modules/*"]
}
},
"files": ["index.ts", "tsImportee.ts", "tsxImportee.tsx"]
}
1 change: 1 addition & 0 deletions tests/importXResolverV3/packages/module-b/tsxImportee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'React Component'
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"include": ["./src", "./shim.d.ts"]
"include": ["./src"]
}
Loading

0 comments on commit 93ea130

Please sign in to comment.