Skip to content

Commit

Permalink
feat: add support for using resolver object directly in settings (#159)
Browse files Browse the repository at this point in the history
Co-authored-by: Sukka <isukkaw@gmail.com>
  • Loading branch information
GoodbyeNJN and SukkaW authored Sep 22, 2024
1 parent ab65566 commit 4da5388
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 146 deletions.
5 changes: 5 additions & 0 deletions .changeset/red-rings-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-import-x": minor
---

feat: add support for using resolver object directly in settings
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,40 @@ module.exports = {
}
```

- use the `import` or `require` syntax to directly import the resolver object:

```js
// .eslintrc.mjs
import tsResolver from 'eslint-import-resolver-typescript'
export default {
settings: {
'import-x/resolver': {
name: 'tsResolver', // required, could be any string you like
// enable: false, // optional, defaults to true
options: { someConfig: value }, // optional, options to pass to the resolver
resolver: tsResolver, // required, the resolver object
},
},
}
```

```js
// .eslintrc.cjs
const tsResolver = require('eslint-import-resolver-typescript')
module.exports = {
settings: {
'import-x/resolver': {
name: 'tsResolver', // required, could be any string you like
// enable: false, // optional, defaults to true
options: { someConfig: value }, // optional, options to pass to the resolver
resolver: tsResolver, // required, the resolver object
},
},
}
```

Relative paths will be resolved relative to the source's nearest `package.json` or
the process's current working directory if no `package.json` is found.

Expand Down
75 changes: 67 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,73 @@ export type DocStyle = 'jsdoc' | 'tomdoc'

export type Arrayable<T> = T | readonly T[]

export type ResultNotFound = {
found: false
path?: undefined
}

export type ResultFound = {
found: true
path: string | null
}

export type ResolvedResult = ResultNotFound | ResultFound

export type ResolverResolve<T = unknown> = (
modulePath: string,
sourceFile: string,
config: T,
) => ResolvedResult

export type ResolverResolveImport<T = unknown> = (
modulePath: string,
sourceFile: string,
config: T,
) => string | undefined

export type Resolver<T = unknown, U = T> = {
interfaceVersion?: 1 | 2
resolve: ResolverResolve<T>
resolveImport: ResolverResolveImport<U>
}

export type ResolverName = LiteralUnion<
'node' | 'typescript' | 'webpack',
string
>

export type ResolverRecord = {
node?: boolean | NodeResolverOptions
typescript?: boolean | TsResolverOptions
webpack?: WebpackResolverOptions
[resolve: string]: unknown
}

export type ResolverObject = {
// node, typescript, webpack...
name: ResolverName

// Enabled by default
enable?: boolean

// Options passed to the resolver
options?:
| NodeResolverOptions
| TsResolverOptions
| WebpackResolverOptions
| unknown

// Any object satisfied Resolver type
resolver: Resolver
}

export type ImportResolver =
| LiteralUnion<'node' | 'typescript' | 'webpack', string>
| {
node?: boolean | NodeResolverOptions
typescript?: boolean | TsResolverOptions
webpack?: WebpackResolverOptions
[resolve: string]: unknown
}
| ResolverName
| ResolverRecord
| ResolverObject
| ResolverName[]
| ResolverRecord[]
| ResolverObject[]

export type ImportSettings = {
cache?: {
Expand All @@ -53,7 +112,7 @@ export type ImportSettings = {
internalRegex?: string
parsers?: Record<string, readonly FileExtension[]>
resolve?: NodeResolverOptions
resolver?: Arrayable<ImportResolver>
resolver?: ImportResolver
}

export type WithPluginName<T extends string | object> = T extends string
Expand Down
115 changes: 59 additions & 56 deletions src/utils/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,18 @@ import path from 'node:path'
import stableHash from 'stable-hash'

import type {
Arrayable,
ImportResolver,
ImportSettings,
PluginSettings,
RuleContext,
Resolver,
ImportResolver,
ResolverRecord,
ResolverObject,
} from '../types'

import { ModuleCache } from './module-cache'
import { pkgDir } from './pkg-dir'

export type ResultNotFound = {
found: false
path?: undefined
}

export type ResultFound = {
found: true
path: string | null
}

export type ResolvedResult = ResultNotFound | ResultFound

export type ResolverResolve = (
modulePath: string,
sourceFile: string,
config: unknown,
) => ResolvedResult

export type ResolverResolveImport = (
modulePath: string,
sourceFile: string,
config: unknown,
) => string | undefined

export type Resolver = {
interfaceVersion?: 1 | 2
resolve: ResolverResolve
resolveImport: ResolverResolveImport
}

export const CASE_SENSITIVE_FS = !fs.existsSync(
path.resolve(
__dirname,
Expand Down Expand Up @@ -184,11 +156,14 @@ function fullResolve(
node: settings['import-x/resolve'],
} // backward compatibility

const resolvers = resolverReducer(configResolvers, new Map())
const resolvers = normalizeConfigResolvers(configResolvers, sourceFile)

for (const { enable, options, resolver } of resolvers) {
if (!enable) {
continue
}

for (const [name, config] of resolvers) {
const resolver = requireResolver(name, sourceFile)
const resolved = withResolver(resolver, config)
const resolved = withResolver(resolver, options)

if (!resolved.found) {
continue
Expand All @@ -212,30 +187,58 @@ export function relative(
return fullResolve(modulePath, sourceFile, settings).path
}

function resolverReducer(
resolvers: Arrayable<ImportResolver>,
map: Map<string, unknown>,
function normalizeConfigResolvers(
resolvers: ImportResolver,
sourceFile: string,
) {
if (Array.isArray(resolvers)) {
for (const r of resolvers as ImportResolver[]) resolverReducer(r, map)
return map
}

if (typeof resolvers === 'string') {
map.set(resolvers, null)
return map
}

if (typeof resolvers === 'object') {
for (const [key, value] of Object.entries(resolvers)) {
map.set(key, value)
const resolverArray = Array.isArray(resolvers) ? resolvers : [resolvers]
const map = new Map<string, Required<ResolverObject>>()

for (const nameOrRecordOrObject of resolverArray) {
if (typeof nameOrRecordOrObject === 'string') {
const name = nameOrRecordOrObject

map.set(name, {
name,
enable: true,
options: undefined,
resolver: requireResolver(name, sourceFile),
})
} else if (typeof nameOrRecordOrObject === 'object') {
if (nameOrRecordOrObject.name && nameOrRecordOrObject.resolver) {
const object = nameOrRecordOrObject as ResolverObject

const { name, enable = true, options, resolver } = object
map.set(name, { name, enable, options, resolver })
} else {
const record = nameOrRecordOrObject as ResolverRecord

for (const [name, enableOrOptions] of Object.entries(record)) {
if (typeof enableOrOptions === 'boolean') {
map.set(name, {
name,
enable: enableOrOptions,
options: undefined,
resolver: requireResolver(name, sourceFile),
})
} else {
map.set(name, {
name,
enable: true,
options: enableOrOptions,
resolver: requireResolver(name, sourceFile),
})
}
}
}
} else {
const err = new Error('invalid resolver config')
err.name = ERROR_NAME
throw err
}
return map
}

const err = new Error('invalid resolver config')
err.name = ERROR_NAME
throw err
return [...map.values()]
}

function getBaseDir(sourceFile: string): string {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

Loading

0 comments on commit 4da5388

Please sign in to comment.